Análisis Exploratorio de Datos¶

Guillermo Luigui Ubaldo Nieto Angarita

Guia¶

https://spotintelligence.com/2023/09/15/exploratory-data-analysis-nlp/

Diccionario de Datos¶

In [ ]:
 

Problema de Investigación¶

Planteamiento del problema¶

Pregunta de investigación¶

Objetivo General¶

Realizar un análisis exploratorio del corpus periodístico en español proveniente de medios colombianos y mexicanos con el fin de identificar las características lingüísticas, temáticas y estructurales que influyen en la generación de preguntas y respuestas automáticas y en el rendimiento de los modelos de Question Answering (QA) durante su proceso de optimización y fine-tuning.

Objetivos Específicos¶

  1. Identificar las variedades lingüísticas y diferencias léxicas entre los textos periodísticos de Colombia y México.

    • Propósito: Determinar cómo las diferencias regionales del español (léxico, sintaxis, uso de expresiones y entidades) pueden influir en la comprensión automática de los modelos QA.

    • Técnicas NLP asociadas: tokenización, lematización, análisis morfosintáctico (POS tagging), TF-IDF, y Named Entity Recognition (NER).

  1. Analizar las secciones periodísticas y temas predominantes para caracterizar los estilos discursivos presentes en el corpus.

    • Propósito: Explorar cómo las diferencias temáticas y estructurales por sección (Política, Economía, Cultura, Sociedad, etc.) pueden afectar la formulación y tipo de preguntas generables automáticamente.

    • Técnicas NLP asociadas: modelado de tópicos (LDA o BERTopic), análisis de frecuencia de palabras, longitud de textos y complejidad léxica.

  1. Analizar las secciones periodísticas y temas predominantes para caracterizar los estilos discursivos presentes en el corpus.

    • Propósito: Explorar cómo las diferencias temáticas y estructurales por sección (Política, Economía, Cultura, Sociedad, etc.) pueden afectar la formulación y tipo de preguntas generables automáticamente.

    • Técnicas NLP asociadas: modelado de tópicos (LDA o BERTopic), análisis de frecuencia de palabras, longitud de textos y complejidad léxica.

  1. Identificar patrones de entidades nombradas (NER) relevantes en los textos periodísticos.

    • Propósito: Analizar la presencia y frecuencia de entidades (PERSON, ORG, LOC, DATE, EVENT) por país y sección para detectar potenciales focos de información factual y temas para la generación de preguntas automáticas.

    • Técnicas NLP asociadas: reconocimiento de entidades, conteo de frecuencias y análisis de coocurrencia entre entidades.

  1. Determinar qué tipos de preguntas y respuestas pueden generarse automáticamente a partir del análisis lingüístico y semántico del corpus.

    • Propósito: Definir el conjunto de patrones de generación QA (qué, quién, cuándo, dónde, por qué, cómo) que mejor se adapten al dominio periodístico y evaluar su distribución esperada.

    • Técnicas NLP asociadas: análisis semántico, detección de relaciones sujeto-predicado, dependency parsing, y modelos generativos (por ejemplo, T5 o GPT).

Resultado esperado del EDA¶

  • Identificación de las diferencias lingüísticas y estilísticas entre noticias colombianas y mexicanas.

  • Determinación de los tipos de preguntas factuales e inferenciales que pueden generarse automáticamente.

  • Hipótesis fundamentada sobre qué modelo y enfoque de fine-tuning podría alcanzar mejor rendimiento en comprensión lectora periodística según país o sección.

Paso 1: Cargar Librerías¶

In [1]:
!python -m spacy download es_core_news_lg
Collecting es-core-news-lg==3.8.0
  Downloading https://github.com/explosion/spacy-models/releases/download/es_core_news_lg-3.8.0/es_core_news_lg-3.8.0-py3-none-any.whl (568.0 MB)
     ---------------------------------------- 0.0/568.0 MB ? eta -:--:--
     --------------------------------------- 1.8/568.0 MB 25.4 MB/s eta 0:00:23
     --------------------------------------- 5.5/568.0 MB 19.8 MB/s eta 0:00:29
     --------------------------------------- 6.8/568.0 MB 14.0 MB/s eta 0:00:41
      -------------------------------------- 7.6/568.0 MB 11.5 MB/s eta 0:00:49
      --------------------------------------- 8.4/568.0 MB 9.1 MB/s eta 0:01:02
      --------------------------------------- 9.2/568.0 MB 8.3 MB/s eta 0:01:08
      -------------------------------------- 10.0/568.0 MB 7.5 MB/s eta 0:01:15
      -------------------------------------- 11.3/568.0 MB 7.0 MB/s eta 0:01:19
      -------------------------------------- 11.8/568.0 MB 6.8 MB/s eta 0:01:22
      -------------------------------------- 12.3/568.0 MB 6.4 MB/s eta 0:01:28
      -------------------------------------- 13.1/568.0 MB 5.9 MB/s eta 0:01:34
      -------------------------------------- 13.6/568.0 MB 5.7 MB/s eta 0:01:38
      -------------------------------------- 14.2/568.0 MB 5.3 MB/s eta 0:01:44
     - ------------------------------------- 14.7/568.0 MB 5.1 MB/s eta 0:01:48
     - ------------------------------------- 15.2/568.0 MB 5.0 MB/s eta 0:01:51
     - ------------------------------------- 16.3/568.0 MB 5.0 MB/s eta 0:01:50
     - ------------------------------------- 17.0/568.0 MB 5.0 MB/s eta 0:01:51
     - ------------------------------------- 17.8/568.0 MB 4.9 MB/s eta 0:01:53
     - ------------------------------------- 18.6/568.0 MB 4.8 MB/s eta 0:01:55
     - ------------------------------------- 19.7/568.0 MB 4.8 MB/s eta 0:01:54
     - ------------------------------------- 21.0/568.0 MB 4.9 MB/s eta 0:01:52
     - ------------------------------------- 21.8/568.0 MB 4.8 MB/s eta 0:01:53
     - ------------------------------------- 22.8/568.0 MB 4.8 MB/s eta 0:01:54
     - ------------------------------------- 23.3/568.0 MB 4.8 MB/s eta 0:01:55
     - ------------------------------------- 24.1/568.0 MB 4.7 MB/s eta 0:01:57
     - ------------------------------------- 24.6/568.0 MB 4.6 MB/s eta 0:01:59
     - ------------------------------------- 25.2/568.0 MB 4.5 MB/s eta 0:02:00
     - ------------------------------------- 26.0/568.0 MB 4.5 MB/s eta 0:02:01
     - ------------------------------------- 26.7/568.0 MB 4.4 MB/s eta 0:02:02
     - ------------------------------------- 27.3/568.0 MB 4.4 MB/s eta 0:02:03
     - ------------------------------------- 27.8/568.0 MB 4.3 MB/s eta 0:02:05
     - ------------------------------------- 28.3/568.0 MB 4.3 MB/s eta 0:02:06
     - ------------------------------------- 28.8/568.0 MB 4.3 MB/s eta 0:02:07
     -- ------------------------------------ 29.4/568.0 MB 4.2 MB/s eta 0:02:09
     -- ------------------------------------ 30.1/568.0 MB 4.1 MB/s eta 0:02:10
     -- ------------------------------------ 30.7/568.0 MB 4.1 MB/s eta 0:02:11
     -- ------------------------------------ 31.7/568.0 MB 4.1 MB/s eta 0:02:10
     -- ------------------------------------ 33.3/568.0 MB 4.2 MB/s eta 0:02:07
     -- ------------------------------------ 34.3/568.0 MB 4.2 MB/s eta 0:02:07
     -- ------------------------------------ 35.7/568.0 MB 4.3 MB/s eta 0:02:05
     -- ------------------------------------ 37.0/568.0 MB 4.3 MB/s eta 0:02:03
     -- ------------------------------------ 38.0/568.0 MB 4.4 MB/s eta 0:02:02
     -- ------------------------------------ 38.8/568.0 MB 4.3 MB/s eta 0:02:03
     -- ------------------------------------ 39.3/568.0 MB 4.3 MB/s eta 0:02:03
     -- ------------------------------------ 40.1/568.0 MB 4.3 MB/s eta 0:02:04
     -- ------------------------------------ 40.6/568.0 MB 4.2 MB/s eta 0:02:05
     -- ------------------------------------ 41.7/568.0 MB 4.3 MB/s eta 0:02:04
     -- ------------------------------------ 43.0/568.0 MB 4.3 MB/s eta 0:02:03
     --- ----------------------------------- 44.6/568.0 MB 4.4 MB/s eta 0:02:01
     --- ----------------------------------- 45.4/568.0 MB 4.4 MB/s eta 0:02:00
     --- ----------------------------------- 46.1/568.0 MB 4.3 MB/s eta 0:02:01
     --- ----------------------------------- 47.2/568.0 MB 4.4 MB/s eta 0:02:00
     --- ----------------------------------- 48.0/568.0 MB 4.3 MB/s eta 0:02:00
     --- ----------------------------------- 48.8/568.0 MB 4.3 MB/s eta 0:02:01
     --- ----------------------------------- 49.5/568.0 MB 4.3 MB/s eta 0:02:01
     --- ----------------------------------- 50.3/568.0 MB 4.3 MB/s eta 0:02:01
     --- ----------------------------------- 51.6/568.0 MB 4.3 MB/s eta 0:02:00
     --- ----------------------------------- 52.4/568.0 MB 4.3 MB/s eta 0:02:00
     --- ----------------------------------- 53.5/568.0 MB 4.3 MB/s eta 0:02:00
     --- ----------------------------------- 54.3/568.0 MB 4.3 MB/s eta 0:01:59
     --- ----------------------------------- 55.1/568.0 MB 4.3 MB/s eta 0:01:59
     --- ----------------------------------- 55.8/568.0 MB 4.3 MB/s eta 0:02:00
     --- ----------------------------------- 56.9/568.0 MB 4.3 MB/s eta 0:01:59
     --- ----------------------------------- 58.2/568.0 MB 4.3 MB/s eta 0:01:58
     ---- ---------------------------------- 59.2/568.0 MB 4.3 MB/s eta 0:01:57
     ---- ---------------------------------- 60.3/568.0 MB 4.4 MB/s eta 0:01:57
     ---- ---------------------------------- 60.8/568.0 MB 4.3 MB/s eta 0:01:57
     ---- ---------------------------------- 61.1/568.0 MB 4.3 MB/s eta 0:01:58
     ---- ---------------------------------- 61.6/568.0 MB 4.3 MB/s eta 0:01:59
     ---- ---------------------------------- 61.9/568.0 MB 4.3 MB/s eta 0:01:59
     ---- ---------------------------------- 62.4/568.0 MB 4.2 MB/s eta 0:02:01
     ---- ---------------------------------- 63.2/568.0 MB 4.2 MB/s eta 0:02:01
     ---- ---------------------------------- 64.0/568.0 MB 4.2 MB/s eta 0:02:01
     ---- ---------------------------------- 65.3/568.0 MB 4.2 MB/s eta 0:02:00
     ---- ---------------------------------- 66.6/568.0 MB 4.2 MB/s eta 0:01:59
     ---- ---------------------------------- 68.7/568.0 MB 4.3 MB/s eta 0:01:56
     ---- ---------------------------------- 69.5/568.0 MB 4.3 MB/s eta 0:01:56
     ---- ---------------------------------- 70.0/568.0 MB 4.3 MB/s eta 0:01:56
     ---- ---------------------------------- 70.8/568.0 MB 4.3 MB/s eta 0:01:57
     ---- ---------------------------------- 71.3/568.0 MB 4.2 MB/s eta 0:01:57
     ---- ---------------------------------- 71.8/568.0 MB 4.2 MB/s eta 0:01:58
     ---- ---------------------------------- 72.4/568.0 MB 4.2 MB/s eta 0:01:58
     ----- --------------------------------- 72.9/568.0 MB 4.2 MB/s eta 0:01:58
     ----- --------------------------------- 73.4/568.0 MB 4.2 MB/s eta 0:01:59
     ----- --------------------------------- 73.9/568.0 MB 4.2 MB/s eta 0:01:59
     ----- --------------------------------- 75.2/568.0 MB 4.2 MB/s eta 0:01:59
     ----- --------------------------------- 76.0/568.0 MB 4.2 MB/s eta 0:01:59
     ----- --------------------------------- 77.1/568.0 MB 4.2 MB/s eta 0:01:58
     ----- --------------------------------- 77.9/568.0 MB 4.2 MB/s eta 0:01:58
     ----- --------------------------------- 78.6/568.0 MB 4.2 MB/s eta 0:01:58
     ----- --------------------------------- 79.7/568.0 MB 4.2 MB/s eta 0:01:57
     ----- --------------------------------- 80.0/568.0 MB 4.1 MB/s eta 0:01:58
     ----- --------------------------------- 80.2/568.0 MB 4.1 MB/s eta 0:01:59
     ----- --------------------------------- 80.5/568.0 MB 4.1 MB/s eta 0:01:59
     ----- --------------------------------- 81.0/568.0 MB 4.1 MB/s eta 0:02:00
     ----- --------------------------------- 81.5/568.0 MB 4.0 MB/s eta 0:02:01
     ----- --------------------------------- 81.8/568.0 MB 4.0 MB/s eta 0:02:01
     ----- --------------------------------- 82.3/568.0 MB 4.0 MB/s eta 0:02:02
     ----- --------------------------------- 83.1/568.0 MB 4.0 MB/s eta 0:02:02
     ----- --------------------------------- 84.4/568.0 MB 4.0 MB/s eta 0:02:01
     ----- --------------------------------- 85.2/568.0 MB 4.0 MB/s eta 0:02:01
     ----- --------------------------------- 86.2/568.0 MB 4.0 MB/s eta 0:02:00
     ----- --------------------------------- 87.3/568.0 MB 4.0 MB/s eta 0:02:00
     ------ -------------------------------- 88.3/568.0 MB 4.0 MB/s eta 0:01:59
     ------ -------------------------------- 89.4/568.0 MB 4.0 MB/s eta 0:01:59
     ------ -------------------------------- 90.4/568.0 MB 4.1 MB/s eta 0:01:58
     ------ -------------------------------- 91.8/568.0 MB 4.1 MB/s eta 0:01:57
     ------ -------------------------------- 92.3/568.0 MB 4.1 MB/s eta 0:01:57
     ------ -------------------------------- 92.5/568.0 MB 4.0 MB/s eta 0:01:58
     ------ -------------------------------- 92.8/568.0 MB 4.0 MB/s eta 0:01:59
     ------ -------------------------------- 93.1/568.0 MB 4.0 MB/s eta 0:01:59
     ------ -------------------------------- 93.6/568.0 MB 4.0 MB/s eta 0:01:59
     ------ -------------------------------- 93.8/568.0 MB 4.0 MB/s eta 0:02:00
     ------ -------------------------------- 94.4/568.0 MB 3.9 MB/s eta 0:02:01
     ------ -------------------------------- 94.6/568.0 MB 3.9 MB/s eta 0:02:01
     ------ -------------------------------- 95.2/568.0 MB 3.9 MB/s eta 0:02:01
     ------ -------------------------------- 95.9/568.0 MB 3.9 MB/s eta 0:02:01
     ------ -------------------------------- 96.7/568.0 MB 3.9 MB/s eta 0:02:01
     ------ -------------------------------- 98.3/568.0 MB 3.9 MB/s eta 0:02:00
     ------ -------------------------------- 99.6/568.0 MB 4.0 MB/s eta 0:01:59
     ------ ------------------------------- 100.1/568.0 MB 3.9 MB/s eta 0:01:59
     ------ ------------------------------- 100.7/568.0 MB 3.9 MB/s eta 0:01:59
     ------ ------------------------------- 101.7/568.0 MB 3.9 MB/s eta 0:01:59
     ------ ------------------------------- 102.5/568.0 MB 3.9 MB/s eta 0:01:59
     ------ ------------------------------- 103.0/568.0 MB 3.9 MB/s eta 0:01:59
     ------ ------------------------------- 103.3/568.0 MB 3.9 MB/s eta 0:01:59
     ------ ------------------------------- 103.8/568.0 MB 3.9 MB/s eta 0:02:00
     ------- ------------------------------ 105.1/568.0 MB 3.9 MB/s eta 0:01:59
     ------- ------------------------------ 106.4/568.0 MB 3.9 MB/s eta 0:01:58
     ------- ------------------------------ 107.5/568.0 MB 3.9 MB/s eta 0:01:58
     ------- ------------------------------ 108.5/568.0 MB 3.9 MB/s eta 0:01:57
     ------- ------------------------------ 109.8/568.0 MB 4.0 MB/s eta 0:01:56
     ------- ------------------------------ 110.6/568.0 MB 4.0 MB/s eta 0:01:56
     ------- ------------------------------ 111.1/568.0 MB 3.9 MB/s eta 0:01:56
     ------- ------------------------------ 111.7/568.0 MB 3.9 MB/s eta 0:01:57
     ------- ------------------------------ 112.7/568.0 MB 3.9 MB/s eta 0:01:56
     ------- ------------------------------ 113.5/568.0 MB 3.9 MB/s eta 0:01:56
     ------- ------------------------------ 114.3/568.0 MB 3.9 MB/s eta 0:01:56
     ------- ------------------------------ 115.3/568.0 MB 3.9 MB/s eta 0:01:55
     ------- ------------------------------ 116.1/568.0 MB 3.9 MB/s eta 0:01:55
     ------- ------------------------------ 116.9/568.0 MB 3.9 MB/s eta 0:01:55
     ------- ------------------------------ 117.7/568.0 MB 3.9 MB/s eta 0:01:55
     ------- ------------------------------ 119.3/568.0 MB 4.0 MB/s eta 0:01:54
     -------- ----------------------------- 121.4/568.0 MB 3.9 MB/s eta 0:01:56
     -------- ----------------------------- 122.2/568.0 MB 3.8 MB/s eta 0:01:56
     -------- ----------------------------- 122.9/568.0 MB 3.8 MB/s eta 0:01:56
     -------- ----------------------------- 123.5/568.0 MB 3.8 MB/s eta 0:01:56
     -------- ----------------------------- 124.5/568.0 MB 3.8 MB/s eta 0:01:56
     -------- ----------------------------- 125.3/568.0 MB 3.8 MB/s eta 0:01:56
     -------- ----------------------------- 125.8/568.0 MB 3.8 MB/s eta 0:01:55
     -------- ----------------------------- 126.4/568.0 MB 3.8 MB/s eta 0:01:56
     -------- ----------------------------- 127.1/568.0 MB 3.8 MB/s eta 0:01:56
     -------- ----------------------------- 127.4/568.0 MB 3.8 MB/s eta 0:01:56
     -------- ----------------------------- 127.9/568.0 MB 3.8 MB/s eta 0:01:56
     -------- ----------------------------- 128.7/568.0 MB 3.8 MB/s eta 0:01:55
     -------- ----------------------------- 129.8/568.0 MB 3.8 MB/s eta 0:01:55
     -------- ----------------------------- 130.8/568.0 MB 3.9 MB/s eta 0:01:54
     -------- ----------------------------- 132.9/568.0 MB 3.9 MB/s eta 0:01:53
     -------- ----------------------------- 134.2/568.0 MB 3.9 MB/s eta 0:01:52
     --------- ---------------------------- 136.1/568.0 MB 3.9 MB/s eta 0:01:50
     --------- ---------------------------- 136.8/568.0 MB 3.9 MB/s eta 0:01:50
     --------- ---------------------------- 137.6/568.0 MB 3.9 MB/s eta 0:01:50
     --------- ---------------------------- 138.9/568.0 MB 3.9 MB/s eta 0:01:50
     --------- ---------------------------- 140.8/568.0 MB 4.0 MB/s eta 0:01:48
     --------- ---------------------------- 142.1/568.0 MB 4.0 MB/s eta 0:01:48
     --------- ---------------------------- 143.7/568.0 MB 4.0 MB/s eta 0:01:46
     --------- ---------------------------- 145.0/568.0 MB 4.0 MB/s eta 0:01:45
     --------- ---------------------------- 145.8/568.0 MB 4.0 MB/s eta 0:01:45
     --------- ---------------------------- 146.3/568.0 MB 4.0 MB/s eta 0:01:45
     --------- ---------------------------- 146.8/568.0 MB 4.0 MB/s eta 0:01:45
     --------- ---------------------------- 147.6/568.0 MB 4.0 MB/s eta 0:01:45
     --------- ---------------------------- 148.1/568.0 MB 4.0 MB/s eta 0:01:45
     --------- ---------------------------- 149.2/568.0 MB 4.0 MB/s eta 0:01:44
     ---------- --------------------------- 149.9/568.0 MB 4.1 MB/s eta 0:01:44
     ---------- --------------------------- 150.7/568.0 MB 4.1 MB/s eta 0:01:43
     ---------- --------------------------- 151.3/568.0 MB 4.1 MB/s eta 0:01:43
     ---------- --------------------------- 152.3/568.0 MB 4.1 MB/s eta 0:01:43
     ---------- --------------------------- 154.4/568.0 MB 4.1 MB/s eta 0:01:41
     ---------- --------------------------- 156.2/568.0 MB 4.2 MB/s eta 0:01:40
     ---------- --------------------------- 157.0/568.0 MB 4.1 MB/s eta 0:01:40
     ---------- --------------------------- 157.8/568.0 MB 4.1 MB/s eta 0:01:40
     ---------- --------------------------- 158.9/568.0 MB 4.1 MB/s eta 0:01:40
     ---------- --------------------------- 159.4/568.0 MB 4.1 MB/s eta 0:01:41
     ---------- --------------------------- 159.9/568.0 MB 4.1 MB/s eta 0:01:41
     ---------- --------------------------- 160.7/568.0 MB 4.1 MB/s eta 0:01:41
     ---------- --------------------------- 161.7/568.0 MB 4.1 MB/s eta 0:01:40
     ---------- --------------------------- 162.8/568.0 MB 4.1 MB/s eta 0:01:40
     ---------- --------------------------- 163.6/568.0 MB 4.1 MB/s eta 0:01:39
     ---------- --------------------------- 164.4/568.0 MB 4.1 MB/s eta 0:01:39
     ----------- -------------------------- 165.4/568.0 MB 4.1 MB/s eta 0:01:39
     ----------- -------------------------- 166.7/568.0 MB 4.1 MB/s eta 0:01:39
     ----------- -------------------------- 168.3/568.0 MB 4.1 MB/s eta 0:01:38
     ----------- -------------------------- 168.8/568.0 MB 4.1 MB/s eta 0:01:38
     ----------- -------------------------- 170.7/568.0 MB 4.1 MB/s eta 0:01:37
     ----------- -------------------------- 172.5/568.0 MB 4.2 MB/s eta 0:01:36
     ----------- -------------------------- 174.1/568.0 MB 4.2 MB/s eta 0:01:35
     ----------- -------------------------- 174.6/568.0 MB 4.2 MB/s eta 0:01:34
     ----------- -------------------------- 175.4/568.0 MB 4.2 MB/s eta 0:01:35
     ----------- -------------------------- 175.9/568.0 MB 4.2 MB/s eta 0:01:35
     ----------- -------------------------- 176.4/568.0 MB 4.1 MB/s eta 0:01:35
     ----------- -------------------------- 177.2/568.0 MB 4.1 MB/s eta 0:01:35
     ----------- -------------------------- 177.7/568.0 MB 4.1 MB/s eta 0:01:35
     ----------- -------------------------- 178.5/568.0 MB 4.1 MB/s eta 0:01:35
     ----------- -------------------------- 179.0/568.0 MB 4.1 MB/s eta 0:01:35
     ------------ ------------------------- 179.8/568.0 MB 4.1 MB/s eta 0:01:35
     ------------ ------------------------- 181.1/568.0 MB 4.1 MB/s eta 0:01:35
     ------------ ------------------------- 182.2/568.0 MB 4.1 MB/s eta 0:01:34
     ------------ ------------------------- 183.0/568.0 MB 4.1 MB/s eta 0:01:34
     ------------ ------------------------- 184.3/568.0 MB 4.1 MB/s eta 0:01:34
     ------------ ------------------------- 185.3/568.0 MB 4.1 MB/s eta 0:01:33
     ------------ ------------------------- 186.1/568.0 MB 4.2 MB/s eta 0:01:32
     ------------ ------------------------- 186.9/568.0 MB 4.2 MB/s eta 0:01:32
     ------------ ------------------------- 188.5/568.0 MB 4.2 MB/s eta 0:01:31
     ------------ ------------------------- 190.1/568.0 MB 4.2 MB/s eta 0:01:30
     ------------ ------------------------- 191.1/568.0 MB 4.2 MB/s eta 0:01:29
     ------------ ------------------------- 191.6/568.0 MB 4.2 MB/s eta 0:01:30
     ------------ ------------------------- 192.2/568.0 MB 4.2 MB/s eta 0:01:30
     ------------ ------------------------- 192.9/568.0 MB 4.2 MB/s eta 0:01:31
     ------------ ------------------------- 194.0/568.0 MB 4.2 MB/s eta 0:01:31
     ------------- ------------------------ 195.6/568.0 MB 4.2 MB/s eta 0:01:29
     ------------- ------------------------ 197.1/568.0 MB 4.2 MB/s eta 0:01:28
     ------------- ------------------------ 198.7/568.0 MB 4.2 MB/s eta 0:01:27
     ------------- ------------------------ 199.5/568.0 MB 4.3 MB/s eta 0:01:27
     ------------- ------------------------ 200.0/568.0 MB 4.3 MB/s eta 0:01:27
     ------------- ------------------------ 200.8/568.0 MB 4.3 MB/s eta 0:01:26
     ------------- ------------------------ 201.3/568.0 MB 4.3 MB/s eta 0:01:26
     ------------- ------------------------ 202.4/568.0 MB 4.3 MB/s eta 0:01:26
     ------------- ------------------------ 203.9/568.0 MB 4.3 MB/s eta 0:01:25
     ------------- ------------------------ 205.5/568.0 MB 4.3 MB/s eta 0:01:24
     ------------- ------------------------ 207.6/568.0 MB 4.4 MB/s eta 0:01:23
     ------------- ------------------------ 208.9/568.0 MB 4.4 MB/s eta 0:01:23
     -------------- ----------------------- 209.7/568.0 MB 4.4 MB/s eta 0:01:22
     -------------- ----------------------- 210.8/568.0 MB 4.4 MB/s eta 0:01:22
     -------------- ----------------------- 211.8/568.0 MB 4.4 MB/s eta 0:01:21
     -------------- ----------------------- 213.6/568.0 MB 4.5 MB/s eta 0:01:20
     -------------- ----------------------- 215.2/568.0 MB 4.5 MB/s eta 0:01:19
     -------------- ----------------------- 215.7/568.0 MB 4.5 MB/s eta 0:01:19
     -------------- ----------------------- 216.5/568.0 MB 4.5 MB/s eta 0:01:18
     -------------- ----------------------- 217.3/568.0 MB 4.5 MB/s eta 0:01:18
     -------------- ----------------------- 217.8/568.0 MB 4.5 MB/s eta 0:01:18
     -------------- ----------------------- 219.2/568.0 MB 4.5 MB/s eta 0:01:17
     -------------- ----------------------- 219.7/568.0 MB 4.5 MB/s eta 0:01:17
     -------------- ----------------------- 220.2/568.0 MB 4.5 MB/s eta 0:01:18
     -------------- ----------------------- 221.0/568.0 MB 4.5 MB/s eta 0:01:18
     -------------- ----------------------- 222.0/568.0 MB 4.5 MB/s eta 0:01:17
     -------------- ----------------------- 222.8/568.0 MB 4.5 MB/s eta 0:01:17
     -------------- ----------------------- 223.6/568.0 MB 4.5 MB/s eta 0:01:17
     -------------- ----------------------- 224.1/568.0 MB 4.5 MB/s eta 0:01:17
     --------------- ---------------------- 224.7/568.0 MB 4.4 MB/s eta 0:01:18
     --------------- ---------------------- 225.2/568.0 MB 4.4 MB/s eta 0:01:18
     --------------- ---------------------- 225.7/568.0 MB 4.5 MB/s eta 0:01:17
     --------------- ---------------------- 226.5/568.0 MB 4.5 MB/s eta 0:01:17
     --------------- ---------------------- 227.8/568.0 MB 4.5 MB/s eta 0:01:16
     --------------- ---------------------- 229.1/568.0 MB 4.5 MB/s eta 0:01:16
     --------------- ---------------------- 229.6/568.0 MB 4.5 MB/s eta 0:01:15
     --------------- ---------------------- 230.4/568.0 MB 4.5 MB/s eta 0:01:15
     --------------- ---------------------- 231.2/568.0 MB 4.6 MB/s eta 0:01:14
     --------------- ---------------------- 232.8/568.0 MB 4.6 MB/s eta 0:01:14
     --------------- ---------------------- 234.6/568.0 MB 4.6 MB/s eta 0:01:13
     --------------- ---------------------- 235.4/568.0 MB 4.6 MB/s eta 0:01:12
     --------------- ---------------------- 235.7/568.0 MB 4.6 MB/s eta 0:01:13
     --------------- ---------------------- 236.5/568.0 MB 4.6 MB/s eta 0:01:13
     --------------- ---------------------- 237.5/568.0 MB 4.6 MB/s eta 0:01:13
     --------------- ---------------------- 238.8/568.0 MB 4.6 MB/s eta 0:01:12
     ---------------- --------------------- 239.6/568.0 MB 4.6 MB/s eta 0:01:12
     ---------------- --------------------- 241.2/568.0 MB 4.6 MB/s eta 0:01:11
     ---------------- --------------------- 242.7/568.0 MB 4.7 MB/s eta 0:01:10
     ---------------- --------------------- 243.8/568.0 MB 4.7 MB/s eta 0:01:10
     ---------------- --------------------- 244.8/568.0 MB 4.7 MB/s eta 0:01:09
     ---------------- --------------------- 245.4/568.0 MB 4.7 MB/s eta 0:01:09
     ---------------- --------------------- 245.9/568.0 MB 4.7 MB/s eta 0:01:10
     ---------------- --------------------- 246.9/568.0 MB 4.7 MB/s eta 0:01:09
     ---------------- --------------------- 247.7/568.0 MB 4.7 MB/s eta 0:01:09
     ---------------- --------------------- 248.5/568.0 MB 4.6 MB/s eta 0:01:09
     ---------------- --------------------- 249.8/568.0 MB 4.6 MB/s eta 0:01:09
     ---------------- --------------------- 250.9/568.0 MB 4.7 MB/s eta 0:01:09
     ---------------- --------------------- 251.7/568.0 MB 4.7 MB/s eta 0:01:08
     ---------------- --------------------- 252.4/568.0 MB 4.7 MB/s eta 0:01:08
     ---------------- --------------------- 253.5/568.0 MB 4.7 MB/s eta 0:01:08
     ----------------- -------------------- 254.8/568.0 MB 4.7 MB/s eta 0:01:07
     ----------------- -------------------- 255.9/568.0 MB 4.7 MB/s eta 0:01:07
     ----------------- -------------------- 256.6/568.0 MB 4.7 MB/s eta 0:01:07
     ----------------- -------------------- 257.4/568.0 MB 4.7 MB/s eta 0:01:07
     ----------------- -------------------- 258.2/568.0 MB 4.7 MB/s eta 0:01:07
     ----------------- -------------------- 259.5/568.0 MB 4.7 MB/s eta 0:01:06
     ----------------- -------------------- 261.1/568.0 MB 4.7 MB/s eta 0:01:06
     ----------------- -------------------- 262.4/568.0 MB 4.7 MB/s eta 0:01:06
     ----------------- -------------------- 263.2/568.0 MB 4.7 MB/s eta 0:01:06
     ----------------- -------------------- 264.0/568.0 MB 4.7 MB/s eta 0:01:05
     ----------------- -------------------- 264.5/568.0 MB 4.7 MB/s eta 0:01:05
     ----------------- -------------------- 265.3/568.0 MB 4.7 MB/s eta 0:01:05
     ----------------- -------------------- 266.9/568.0 MB 4.7 MB/s eta 0:01:05
     ----------------- -------------------- 267.4/568.0 MB 4.7 MB/s eta 0:01:04
     ----------------- -------------------- 268.2/568.0 MB 4.7 MB/s eta 0:01:04
     ------------------ ------------------- 269.2/568.0 MB 4.7 MB/s eta 0:01:04
     ------------------ ------------------- 270.8/568.0 MB 4.8 MB/s eta 0:01:03
     ------------------ ------------------- 272.4/568.0 MB 4.8 MB/s eta 0:01:02
     ------------------ ------------------- 273.7/568.0 MB 4.8 MB/s eta 0:01:02
     ------------------ ------------------- 274.5/568.0 MB 4.8 MB/s eta 0:01:02
     ------------------ ------------------- 275.0/568.0 MB 4.8 MB/s eta 0:01:02
     ------------------ ------------------- 275.5/568.0 MB 4.7 MB/s eta 0:01:02
     ------------------ ------------------- 276.3/568.0 MB 4.7 MB/s eta 0:01:03
     ------------------ ------------------- 276.8/568.0 MB 4.7 MB/s eta 0:01:03
     ------------------ ------------------- 277.6/568.0 MB 4.7 MB/s eta 0:01:03
     ------------------ ------------------- 278.4/568.0 MB 4.7 MB/s eta 0:01:03
     ------------------ ------------------- 280.0/568.0 MB 4.7 MB/s eta 0:01:02
     ------------------ ------------------- 281.3/568.0 MB 4.7 MB/s eta 0:01:02
     ------------------ ------------------- 282.3/568.0 MB 4.6 MB/s eta 0:01:02
     ------------------ ------------------- 283.1/568.0 MB 4.6 MB/s eta 0:01:02
     ------------------ ------------------- 283.9/568.0 MB 4.6 MB/s eta 0:01:02
     ------------------- ------------------ 285.0/568.0 MB 4.6 MB/s eta 0:01:02
     ------------------- ------------------ 285.7/568.0 MB 4.6 MB/s eta 0:01:01
     ------------------- ------------------ 286.5/568.0 MB 4.6 MB/s eta 0:01:01
     ------------------- ------------------ 287.3/568.0 MB 4.6 MB/s eta 0:01:01
     ------------------- ------------------ 288.1/568.0 MB 4.6 MB/s eta 0:01:01
     ------------------- ------------------ 289.1/568.0 MB 4.7 MB/s eta 0:01:00
     ------------------- ------------------ 290.2/568.0 MB 4.7 MB/s eta 0:01:00
     ------------------- ------------------ 291.0/568.0 MB 4.7 MB/s eta 0:01:00
     ------------------- ------------------ 292.0/568.0 MB 4.7 MB/s eta 0:01:00
     ------------------- ------------------ 292.6/568.0 MB 4.6 MB/s eta 0:01:00
     ------------------- ------------------ 293.1/568.0 MB 4.6 MB/s eta 0:01:01
     ------------------- ------------------ 294.1/568.0 MB 4.6 MB/s eta 0:01:00
     ------------------- ------------------ 295.2/568.0 MB 4.6 MB/s eta 0:01:00
     ------------------- ------------------ 296.0/568.0 MB 4.6 MB/s eta 0:01:00
     ------------------- ------------------ 297.3/568.0 MB 4.6 MB/s eta 0:00:59
     ------------------- ------------------ 298.3/568.0 MB 4.6 MB/s eta 0:00:59
     ------------------- ------------------ 298.8/568.0 MB 4.6 MB/s eta 0:00:59
     -------------------- ----------------- 300.2/568.0 MB 4.6 MB/s eta 0:00:58
     -------------------- ----------------- 302.0/568.0 MB 4.7 MB/s eta 0:00:58
     -------------------- ----------------- 303.6/568.0 MB 4.7 MB/s eta 0:00:57
     -------------------- ----------------- 304.3/568.0 MB 4.7 MB/s eta 0:00:57
     -------------------- ----------------- 304.9/568.0 MB 4.7 MB/s eta 0:00:57
     -------------------- ----------------- 305.4/568.0 MB 4.7 MB/s eta 0:00:57
     -------------------- ----------------- 306.4/568.0 MB 4.6 MB/s eta 0:00:57
     -------------------- ----------------- 307.5/568.0 MB 4.6 MB/s eta 0:00:57
     -------------------- ----------------- 308.0/568.0 MB 4.6 MB/s eta 0:00:57
     -------------------- ----------------- 308.5/568.0 MB 4.6 MB/s eta 0:00:57
     -------------------- ----------------- 309.1/568.0 MB 4.5 MB/s eta 0:00:58
     -------------------- ----------------- 309.6/568.0 MB 4.5 MB/s eta 0:00:58
     -------------------- ----------------- 310.1/568.0 MB 4.5 MB/s eta 0:00:58
     -------------------- ----------------- 311.2/568.0 MB 4.5 MB/s eta 0:00:57
     -------------------- ----------------- 312.2/568.0 MB 4.5 MB/s eta 0:00:57
     -------------------- ----------------- 313.0/568.0 MB 4.5 MB/s eta 0:00:57
     -------------------- ----------------- 313.8/568.0 MB 4.5 MB/s eta 0:00:56
     --------------------- ---------------- 314.6/568.0 MB 4.5 MB/s eta 0:00:56
     --------------------- ---------------- 316.1/568.0 MB 4.6 MB/s eta 0:00:56
     --------------------- ---------------- 317.5/568.0 MB 4.6 MB/s eta 0:00:55
     --------------------- ---------------- 319.0/568.0 MB 4.6 MB/s eta 0:00:54
     --------------------- ---------------- 319.8/568.0 MB 4.6 MB/s eta 0:00:54
     --------------------- ---------------- 320.9/568.0 MB 4.6 MB/s eta 0:00:54
     --------------------- ---------------- 321.7/568.0 MB 4.6 MB/s eta 0:00:54
     --------------------- ---------------- 322.7/568.0 MB 4.6 MB/s eta 0:00:54
     --------------------- ---------------- 324.3/568.0 MB 4.6 MB/s eta 0:00:53
     --------------------- ---------------- 326.6/568.0 MB 4.7 MB/s eta 0:00:52
     ---------------------- --------------- 329.0/568.0 MB 4.7 MB/s eta 0:00:51
     ---------------------- --------------- 330.3/568.0 MB 4.7 MB/s eta 0:00:51
     ---------------------- --------------- 332.1/568.0 MB 4.7 MB/s eta 0:00:51
     ---------------------- --------------- 333.4/568.0 MB 4.7 MB/s eta 0:00:50
     ---------------------- --------------- 334.2/568.0 MB 4.8 MB/s eta 0:00:50
     ---------------------- --------------- 334.8/568.0 MB 4.7 MB/s eta 0:00:50
     ---------------------- --------------- 335.5/568.0 MB 4.7 MB/s eta 0:00:50
     ---------------------- --------------- 336.3/568.0 MB 4.7 MB/s eta 0:00:50
     ---------------------- --------------- 337.1/568.0 MB 4.7 MB/s eta 0:00:50
     ---------------------- --------------- 337.9/568.0 MB 4.7 MB/s eta 0:00:50
     ---------------------- --------------- 338.7/568.0 MB 4.6 MB/s eta 0:00:50
     ---------------------- --------------- 340.5/568.0 MB 4.7 MB/s eta 0:00:49
     ---------------------- --------------- 342.9/568.0 MB 4.7 MB/s eta 0:00:48
     ----------------------- -------------- 344.5/568.0 MB 4.8 MB/s eta 0:00:47
     ----------------------- -------------- 345.2/568.0 MB 4.8 MB/s eta 0:00:47
     ----------------------- -------------- 346.6/568.0 MB 4.8 MB/s eta 0:00:47
     ----------------------- -------------- 347.3/568.0 MB 4.7 MB/s eta 0:00:47
     ----------------------- -------------- 347.9/568.0 MB 4.7 MB/s eta 0:00:47
     ----------------------- -------------- 348.4/568.0 MB 4.7 MB/s eta 0:00:48
     ----------------------- -------------- 348.9/568.0 MB 4.6 MB/s eta 0:00:48
     ----------------------- -------------- 349.7/568.0 MB 4.6 MB/s eta 0:00:47
     ----------------------- -------------- 350.2/568.0 MB 4.6 MB/s eta 0:00:47
     ----------------------- -------------- 351.0/568.0 MB 4.6 MB/s eta 0:00:48
     ----------------------- -------------- 351.3/568.0 MB 4.6 MB/s eta 0:00:48
     ----------------------- -------------- 352.1/568.0 MB 4.6 MB/s eta 0:00:48
     ----------------------- -------------- 353.1/568.0 MB 4.6 MB/s eta 0:00:48
     ----------------------- -------------- 353.6/568.0 MB 4.6 MB/s eta 0:00:48
     ----------------------- -------------- 354.2/568.0 MB 4.5 MB/s eta 0:00:48
     ----------------------- -------------- 354.7/568.0 MB 4.5 MB/s eta 0:00:48
     ----------------------- -------------- 355.5/568.0 MB 4.5 MB/s eta 0:00:47
     ----------------------- -------------- 356.5/568.0 MB 4.6 MB/s eta 0:00:47
     ----------------------- -------------- 357.3/568.0 MB 4.6 MB/s eta 0:00:47
     ----------------------- -------------- 358.1/568.0 MB 4.5 MB/s eta 0:00:47
     ------------------------ ------------- 358.9/568.0 MB 4.5 MB/s eta 0:00:47
     ------------------------ ------------- 360.2/568.0 MB 4.6 MB/s eta 0:00:46
     ------------------------ ------------- 362.0/568.0 MB 4.6 MB/s eta 0:00:45
     ------------------------ ------------- 362.8/568.0 MB 4.6 MB/s eta 0:00:45
     ------------------------ ------------- 363.9/568.0 MB 4.6 MB/s eta 0:00:45
     ------------------------ ------------- 364.6/568.0 MB 4.6 MB/s eta 0:00:44
     ------------------------ ------------- 365.4/568.0 MB 4.6 MB/s eta 0:00:44
     ------------------------ ------------- 366.5/568.0 MB 4.6 MB/s eta 0:00:44
     ------------------------ ------------- 367.8/568.0 MB 4.6 MB/s eta 0:00:44
     ------------------------ ------------- 369.1/568.0 MB 4.7 MB/s eta 0:00:43
     ------------------------ ------------- 370.4/568.0 MB 4.7 MB/s eta 0:00:43
     ------------------------ ------------- 371.5/568.0 MB 4.7 MB/s eta 0:00:43
     ------------------------ ------------- 372.5/568.0 MB 4.7 MB/s eta 0:00:42
     ------------------------- ------------ 373.8/568.0 MB 4.7 MB/s eta 0:00:42
     ------------------------- ------------ 374.9/568.0 MB 4.7 MB/s eta 0:00:42
     ------------------------- ------------ 376.4/568.0 MB 4.7 MB/s eta 0:00:41
     ------------------------- ------------ 378.3/568.0 MB 4.7 MB/s eta 0:00:41
     ------------------------- ------------ 380.1/568.0 MB 4.8 MB/s eta 0:00:40
     ------------------------- ------------ 382.2/568.0 MB 4.8 MB/s eta 0:00:39
     ------------------------- ------------ 383.8/568.0 MB 4.8 MB/s eta 0:00:39
     ------------------------- ------------ 384.6/568.0 MB 4.8 MB/s eta 0:00:39
     ------------------------- ------------ 386.4/568.0 MB 4.8 MB/s eta 0:00:38
     ------------------------- ------------ 387.2/568.0 MB 4.8 MB/s eta 0:00:38
     ------------------------- ------------ 388.0/568.0 MB 4.8 MB/s eta 0:00:38
     -------------------------- ----------- 388.8/568.0 MB 4.8 MB/s eta 0:00:38
     -------------------------- ----------- 390.3/568.0 MB 4.8 MB/s eta 0:00:37
     -------------------------- ----------- 391.1/568.0 MB 4.8 MB/s eta 0:00:37
     -------------------------- ----------- 391.4/568.0 MB 4.8 MB/s eta 0:00:37
     -------------------------- ----------- 391.9/568.0 MB 4.8 MB/s eta 0:00:37
     -------------------------- ----------- 393.2/568.0 MB 4.8 MB/s eta 0:00:37
     -------------------------- ----------- 395.3/568.0 MB 4.8 MB/s eta 0:00:36
     -------------------------- ----------- 397.1/568.0 MB 4.9 MB/s eta 0:00:36
     -------------------------- ----------- 399.2/568.0 MB 4.9 MB/s eta 0:00:35
     -------------------------- ----------- 400.3/568.0 MB 4.9 MB/s eta 0:00:35
     -------------------------- ----------- 401.6/568.0 MB 4.9 MB/s eta 0:00:34
     --------------------------- ---------- 403.7/568.0 MB 4.9 MB/s eta 0:00:34
     --------------------------- ---------- 405.0/568.0 MB 5.0 MB/s eta 0:00:33
     --------------------------- ---------- 405.8/568.0 MB 4.9 MB/s eta 0:00:33
     --------------------------- ---------- 406.6/568.0 MB 4.9 MB/s eta 0:00:33
     --------------------------- ---------- 407.9/568.0 MB 4.9 MB/s eta 0:00:33
     --------------------------- ---------- 409.2/568.0 MB 4.9 MB/s eta 0:00:33
     --------------------------- ---------- 410.8/568.0 MB 4.9 MB/s eta 0:00:32
     --------------------------- ---------- 412.6/568.0 MB 5.0 MB/s eta 0:00:32
     --------------------------- ---------- 413.4/568.0 MB 5.0 MB/s eta 0:00:31
     --------------------------- ---------- 414.2/568.0 MB 5.0 MB/s eta 0:00:31
     --------------------------- ---------- 415.0/568.0 MB 5.0 MB/s eta 0:00:31
     --------------------------- ---------- 415.8/568.0 MB 5.0 MB/s eta 0:00:31
     --------------------------- ---------- 416.5/568.0 MB 5.0 MB/s eta 0:00:31
     --------------------------- ---------- 417.6/568.0 MB 5.0 MB/s eta 0:00:31
     ---------------------------- --------- 418.6/568.0 MB 5.0 MB/s eta 0:00:30
     ---------------------------- --------- 418.9/568.0 MB 5.0 MB/s eta 0:00:31
     ---------------------------- --------- 418.9/568.0 MB 5.0 MB/s eta 0:00:31
     ---------------------------- --------- 419.2/568.0 MB 4.9 MB/s eta 0:00:31
     ---------------------------- --------- 419.4/568.0 MB 4.8 MB/s eta 0:00:31
     ---------------------------- --------- 420.0/568.0 MB 4.8 MB/s eta 0:00:31
     ---------------------------- --------- 421.3/568.0 MB 4.9 MB/s eta 0:00:31
     ---------------------------- --------- 421.8/568.0 MB 4.9 MB/s eta 0:00:31
     ---------------------------- --------- 422.8/568.0 MB 4.9 MB/s eta 0:00:30
     ---------------------------- --------- 424.1/568.0 MB 4.9 MB/s eta 0:00:30
     ---------------------------- --------- 424.9/568.0 MB 4.9 MB/s eta 0:00:30
     ---------------------------- --------- 426.0/568.0 MB 4.9 MB/s eta 0:00:30
     ---------------------------- --------- 426.5/568.0 MB 4.8 MB/s eta 0:00:30
     ---------------------------- --------- 427.6/568.0 MB 4.8 MB/s eta 0:00:29
     ---------------------------- --------- 429.1/568.0 MB 4.9 MB/s eta 0:00:29
     ---------------------------- --------- 430.2/568.0 MB 4.9 MB/s eta 0:00:29
     ---------------------------- --------- 431.0/568.0 MB 4.9 MB/s eta 0:00:29
     ---------------------------- --------- 431.8/568.0 MB 4.9 MB/s eta 0:00:28
     ---------------------------- --------- 432.5/568.0 MB 4.9 MB/s eta 0:00:28
     ----------------------------- -------- 433.8/568.0 MB 4.9 MB/s eta 0:00:28
     ----------------------------- -------- 435.2/568.0 MB 4.9 MB/s eta 0:00:28
     ----------------------------- -------- 436.2/568.0 MB 4.9 MB/s eta 0:00:27
     ----------------------------- -------- 437.5/568.0 MB 4.9 MB/s eta 0:00:27
     ----------------------------- -------- 438.8/568.0 MB 4.9 MB/s eta 0:00:27
     ----------------------------- -------- 439.4/568.0 MB 4.9 MB/s eta 0:00:27
     ----------------------------- -------- 440.1/568.0 MB 4.9 MB/s eta 0:00:27
     ----------------------------- -------- 440.9/568.0 MB 4.9 MB/s eta 0:00:26
     ----------------------------- -------- 441.7/568.0 MB 4.9 MB/s eta 0:00:26
     ----------------------------- -------- 443.0/568.0 MB 4.9 MB/s eta 0:00:26
     ----------------------------- -------- 444.1/568.0 MB 4.9 MB/s eta 0:00:26
     ----------------------------- -------- 445.1/568.0 MB 4.9 MB/s eta 0:00:26
     ----------------------------- -------- 446.2/568.0 MB 4.9 MB/s eta 0:00:25
     ----------------------------- -------- 447.2/568.0 MB 4.9 MB/s eta 0:00:25
     ----------------------------- -------- 448.3/568.0 MB 4.9 MB/s eta 0:00:25
     ------------------------------ ------- 449.6/568.0 MB 4.9 MB/s eta 0:00:25
     ------------------------------ ------- 450.9/568.0 MB 4.9 MB/s eta 0:00:24
     ------------------------------ ------- 451.7/568.0 MB 4.9 MB/s eta 0:00:24
     ------------------------------ ------- 452.2/568.0 MB 4.9 MB/s eta 0:00:24
     ------------------------------ ------- 453.0/568.0 MB 4.9 MB/s eta 0:00:24
     ------------------------------ ------- 454.0/568.0 MB 4.9 MB/s eta 0:00:24
     ------------------------------ ------- 455.1/568.0 MB 4.9 MB/s eta 0:00:23
     ------------------------------ ------- 456.7/568.0 MB 4.9 MB/s eta 0:00:23
     ------------------------------ ------- 457.2/568.0 MB 4.9 MB/s eta 0:00:23
     ------------------------------ ------- 457.7/568.0 MB 4.9 MB/s eta 0:00:23
     ------------------------------ ------- 458.5/568.0 MB 5.0 MB/s eta 0:00:23
     ------------------------------ ------- 459.3/568.0 MB 5.0 MB/s eta 0:00:22
     ------------------------------ ------- 460.1/568.0 MB 4.9 MB/s eta 0:00:22
     ------------------------------ ------- 460.8/568.0 MB 4.9 MB/s eta 0:00:22
     ------------------------------ ------- 462.4/568.0 MB 5.0 MB/s eta 0:00:22
     ------------------------------- ------ 463.5/568.0 MB 5.0 MB/s eta 0:00:22
     ------------------------------- ------ 464.0/568.0 MB 5.0 MB/s eta 0:00:21
     ------------------------------- ------ 464.5/568.0 MB 4.9 MB/s eta 0:00:21
     ------------------------------- ------ 465.0/568.0 MB 4.9 MB/s eta 0:00:21
     ------------------------------- ------ 466.1/568.0 MB 4.9 MB/s eta 0:00:21
     ------------------------------- ------ 466.6/568.0 MB 4.9 MB/s eta 0:00:21
     ------------------------------- ------ 467.1/568.0 MB 4.9 MB/s eta 0:00:21
     ------------------------------- ------ 467.7/568.0 MB 4.9 MB/s eta 0:00:21
     ------------------------------- ------ 469.0/568.0 MB 4.9 MB/s eta 0:00:21
     ------------------------------- ------ 470.5/568.0 MB 4.8 MB/s eta 0:00:21
     ------------------------------- ------ 471.6/568.0 MB 4.8 MB/s eta 0:00:21
     ------------------------------- ------ 472.9/568.0 MB 4.8 MB/s eta 0:00:20
     ------------------------------- ------ 473.7/568.0 MB 4.8 MB/s eta 0:00:20
     ------------------------------- ------ 474.7/568.0 MB 4.7 MB/s eta 0:00:20
     ------------------------------- ------ 475.5/568.0 MB 4.7 MB/s eta 0:00:20
     ------------------------------- ------ 476.3/568.0 MB 4.7 MB/s eta 0:00:20
     ------------------------------- ------ 477.1/568.0 MB 4.7 MB/s eta 0:00:20
     ------------------------------- ------ 477.4/568.0 MB 4.7 MB/s eta 0:00:20
     ------------------------------- ------ 477.9/568.0 MB 4.7 MB/s eta 0:00:20
     -------------------------------- ----- 478.4/568.0 MB 4.7 MB/s eta 0:00:20
     -------------------------------- ----- 479.2/568.0 MB 4.7 MB/s eta 0:00:19
     -------------------------------- ----- 480.2/568.0 MB 4.7 MB/s eta 0:00:19
     -------------------------------- ----- 481.3/568.0 MB 4.7 MB/s eta 0:00:19
     -------------------------------- ----- 481.8/568.0 MB 4.6 MB/s eta 0:00:19
     -------------------------------- ----- 482.9/568.0 MB 4.6 MB/s eta 0:00:19
     -------------------------------- ----- 483.7/568.0 MB 4.6 MB/s eta 0:00:19
     -------------------------------- ----- 485.2/568.0 MB 4.6 MB/s eta 0:00:18
     -------------------------------- ----- 486.8/568.0 MB 4.6 MB/s eta 0:00:18
     -------------------------------- ----- 487.6/568.0 MB 4.7 MB/s eta 0:00:18
     -------------------------------- ----- 488.1/568.0 MB 4.7 MB/s eta 0:00:18
     -------------------------------- ----- 488.9/568.0 MB 4.7 MB/s eta 0:00:17
     -------------------------------- ----- 489.7/568.0 MB 4.7 MB/s eta 0:00:17
     -------------------------------- ----- 491.0/568.0 MB 4.7 MB/s eta 0:00:17
     -------------------------------- ----- 492.0/568.0 MB 4.7 MB/s eta 0:00:17
     --------------------------------- ---- 493.6/568.0 MB 4.7 MB/s eta 0:00:16
     --------------------------------- ---- 495.7/568.0 MB 4.8 MB/s eta 0:00:16
     --------------------------------- ---- 497.3/568.0 MB 4.8 MB/s eta 0:00:15
     --------------------------------- ---- 498.9/568.0 MB 4.8 MB/s eta 0:00:15
     --------------------------------- ---- 500.4/568.0 MB 4.9 MB/s eta 0:00:14
     --------------------------------- ---- 501.2/568.0 MB 4.9 MB/s eta 0:00:14
     --------------------------------- ---- 502.3/568.0 MB 4.9 MB/s eta 0:00:14
     --------------------------------- ---- 503.6/568.0 MB 4.9 MB/s eta 0:00:14
     --------------------------------- ---- 505.2/568.0 MB 4.9 MB/s eta 0:00:13
     --------------------------------- ---- 506.2/568.0 MB 4.9 MB/s eta 0:00:13
     --------------------------------- ---- 507.8/568.0 MB 5.0 MB/s eta 0:00:13
     ---------------------------------- --- 509.9/568.0 MB 5.0 MB/s eta 0:00:12
     ---------------------------------- --- 510.9/568.0 MB 5.0 MB/s eta 0:00:12
     ---------------------------------- --- 511.7/568.0 MB 5.0 MB/s eta 0:00:12
     ---------------------------------- --- 512.2/568.0 MB 5.0 MB/s eta 0:00:12
     ---------------------------------- --- 513.3/568.0 MB 4.9 MB/s eta 0:00:12
     ---------------------------------- --- 514.3/568.0 MB 5.0 MB/s eta 0:00:11
     ---------------------------------- --- 515.6/568.0 MB 5.0 MB/s eta 0:00:11
     ---------------------------------- --- 516.9/568.0 MB 5.0 MB/s eta 0:00:11
     ---------------------------------- --- 517.2/568.0 MB 5.0 MB/s eta 0:00:11
     ---------------------------------- --- 518.0/568.0 MB 4.9 MB/s eta 0:00:11
     ---------------------------------- --- 518.5/568.0 MB 4.9 MB/s eta 0:00:11
     ---------------------------------- --- 519.0/568.0 MB 4.9 MB/s eta 0:00:10
     ---------------------------------- --- 520.4/568.0 MB 4.9 MB/s eta 0:00:10
     ---------------------------------- --- 521.4/568.0 MB 4.9 MB/s eta 0:00:10
     ---------------------------------- --- 521.9/568.0 MB 4.8 MB/s eta 0:00:10
     ---------------------------------- --- 522.5/568.0 MB 4.8 MB/s eta 0:00:10
     ---------------------------------- --- 523.0/568.0 MB 4.8 MB/s eta 0:00:10
     ----------------------------------- -- 523.8/568.0 MB 4.7 MB/s eta 0:00:10
     ----------------------------------- -- 524.3/568.0 MB 4.7 MB/s eta 0:00:10
     ----------------------------------- -- 524.8/568.0 MB 4.7 MB/s eta 0:00:10
     ----------------------------------- -- 525.9/568.0 MB 4.6 MB/s eta 0:00:10
     ----------------------------------- -- 526.4/568.0 MB 4.6 MB/s eta 0:00:09
     ----------------------------------- -- 527.2/568.0 MB 4.6 MB/s eta 0:00:09
     ----------------------------------- -- 528.0/568.0 MB 4.6 MB/s eta 0:00:09
     ----------------------------------- -- 528.7/568.0 MB 4.6 MB/s eta 0:00:09
     ----------------------------------- -- 529.3/568.0 MB 4.6 MB/s eta 0:00:09
     ----------------------------------- -- 529.8/568.0 MB 4.6 MB/s eta 0:00:09
     ----------------------------------- -- 530.8/568.0 MB 4.6 MB/s eta 0:00:09
     ----------------------------------- -- 531.6/568.0 MB 4.6 MB/s eta 0:00:08
     ----------------------------------- -- 532.2/568.0 MB 4.6 MB/s eta 0:00:08
     ----------------------------------- -- 532.7/568.0 MB 4.5 MB/s eta 0:00:08
     ----------------------------------- -- 533.7/568.0 MB 4.5 MB/s eta 0:00:08
     ----------------------------------- -- 534.5/568.0 MB 4.5 MB/s eta 0:00:08
     ----------------------------------- -- 535.0/568.0 MB 4.4 MB/s eta 0:00:08
     ----------------------------------- -- 535.3/568.0 MB 4.4 MB/s eta 0:00:08
     ----------------------------------- -- 536.1/568.0 MB 4.4 MB/s eta 0:00:08
     ----------------------------------- -- 537.1/568.0 MB 4.4 MB/s eta 0:00:08
     ----------------------------------- -- 537.9/568.0 MB 4.4 MB/s eta 0:00:07
     ------------------------------------ - 538.4/568.0 MB 4.4 MB/s eta 0:00:07
     ------------------------------------ - 539.0/568.0 MB 4.3 MB/s eta 0:00:07
     ------------------------------------ - 539.8/568.0 MB 4.3 MB/s eta 0:00:07
     ------------------------------------ - 540.5/568.0 MB 4.3 MB/s eta 0:00:07
     ------------------------------------ - 541.1/568.0 MB 4.3 MB/s eta 0:00:07
     ------------------------------------ - 542.1/568.0 MB 4.3 MB/s eta 0:00:07
     ------------------------------------ - 543.2/568.0 MB 4.3 MB/s eta 0:00:06
     ------------------------------------ - 544.5/568.0 MB 4.3 MB/s eta 0:00:06
     ------------------------------------ - 545.0/568.0 MB 4.3 MB/s eta 0:00:06
     ------------------------------------ - 545.0/568.0 MB 4.3 MB/s eta 0:00:06
     ------------------------------------ - 545.0/568.0 MB 4.3 MB/s eta 0:00:06
     ------------------------------------ - 545.3/568.0 MB 4.2 MB/s eta 0:00:06
     ------------------------------------ - 545.3/568.0 MB 4.2 MB/s eta 0:00:06
     ------------------------------------ - 545.3/568.0 MB 4.2 MB/s eta 0:00:06
     ------------------------------------ - 545.3/568.0 MB 4.2 MB/s eta 0:00:06
     ------------------------------------ - 545.3/568.0 MB 4.2 MB/s eta 0:00:06
     ------------------------------------ - 545.3/568.0 MB 4.2 MB/s eta 0:00:06
     ------------------------------------ - 545.5/568.0 MB 4.1 MB/s eta 0:00:06
     ------------------------------------ - 545.5/568.0 MB 4.1 MB/s eta 0:00:06
     ------------------------------------ - 545.8/568.0 MB 4.1 MB/s eta 0:00:06
     ------------------------------------ - 545.8/568.0 MB 4.1 MB/s eta 0:00:06
     ------------------------------------ - 546.0/568.0 MB 4.0 MB/s eta 0:00:06
     ------------------------------------ - 546.3/568.0 MB 4.0 MB/s eta 0:00:06
     ------------------------------------ - 546.6/568.0 MB 4.0 MB/s eta 0:00:06
     ------------------------------------ - 546.8/568.0 MB 4.0 MB/s eta 0:00:06
     ------------------------------------ - 547.1/568.0 MB 4.0 MB/s eta 0:00:06
     ------------------------------------ - 547.4/568.0 MB 3.9 MB/s eta 0:00:06
     ------------------------------------ - 547.9/568.0 MB 3.9 MB/s eta 0:00:06
     ------------------------------------ - 547.9/568.0 MB 3.9 MB/s eta 0:00:06
     ------------------------------------ - 548.1/568.0 MB 3.9 MB/s eta 0:00:06
     ------------------------------------ - 548.4/568.0 MB 3.8 MB/s eta 0:00:06
     ------------------------------------ - 548.4/568.0 MB 3.8 MB/s eta 0:00:06
     ------------------------------------ - 548.7/568.0 MB 3.8 MB/s eta 0:00:06
     ------------------------------------ - 548.7/568.0 MB 3.8 MB/s eta 0:00:06
     ------------------------------------ - 548.9/568.0 MB 3.7 MB/s eta 0:00:06
     ------------------------------------ - 549.2/568.0 MB 3.7 MB/s eta 0:00:06
     ------------------------------------ - 549.5/568.0 MB 3.6 MB/s eta 0:00:06
     ------------------------------------ - 549.7/568.0 MB 3.6 MB/s eta 0:00:06
     ------------------------------------ - 550.0/568.0 MB 3.6 MB/s eta 0:00:05
     ------------------------------------ - 550.5/568.0 MB 3.6 MB/s eta 0:00:05
     ------------------------------------ - 550.8/568.0 MB 3.6 MB/s eta 0:00:05
     ------------------------------------ - 551.0/568.0 MB 3.6 MB/s eta 0:00:05
     ------------------------------------ - 551.6/568.0 MB 3.5 MB/s eta 0:00:05
     ------------------------------------ - 551.6/568.0 MB 3.5 MB/s eta 0:00:05
     ------------------------------------ - 551.8/568.0 MB 3.5 MB/s eta 0:00:05
     ------------------------------------ - 552.1/568.0 MB 3.5 MB/s eta 0:00:05
     ------------------------------------ - 552.3/568.0 MB 3.4 MB/s eta 0:00:05
     ------------------------------------ - 552.6/568.0 MB 3.4 MB/s eta 0:00:05
     ------------------------------------ - 552.9/568.0 MB 3.4 MB/s eta 0:00:05
     -------------------------------------  553.4/568.0 MB 3.4 MB/s eta 0:00:05
     -------------------------------------  553.6/568.0 MB 3.4 MB/s eta 0:00:05
     -------------------------------------  554.2/568.0 MB 3.3 MB/s eta 0:00:05
     -------------------------------------  554.4/568.0 MB 3.3 MB/s eta 0:00:05
     -------------------------------------  555.0/568.0 MB 3.3 MB/s eta 0:00:04
     -------------------------------------  555.5/568.0 MB 3.3 MB/s eta 0:00:04
     -------------------------------------  555.7/568.0 MB 3.3 MB/s eta 0:00:04
     -------------------------------------  556.3/568.0 MB 3.2 MB/s eta 0:00:04
     -------------------------------------  556.5/568.0 MB 3.2 MB/s eta 0:00:04
     -------------------------------------  557.1/568.0 MB 3.2 MB/s eta 0:00:04
     -------------------------------------  557.6/568.0 MB 3.2 MB/s eta 0:00:04
     -------------------------------------  558.1/568.0 MB 3.2 MB/s eta 0:00:04
     -------------------------------------  558.4/568.0 MB 3.2 MB/s eta 0:00:04
     -------------------------------------  558.4/568.0 MB 3.2 MB/s eta 0:00:04
     -------------------------------------  558.4/568.0 MB 3.2 MB/s eta 0:00:04
     -------------------------------------  558.4/568.0 MB 3.2 MB/s eta 0:00:04
     -------------------------------------  558.6/568.0 MB 3.1 MB/s eta 0:00:04
     -------------------------------------  559.2/568.0 MB 3.1 MB/s eta 0:00:03
     -------------------------------------  560.2/568.0 MB 3.1 MB/s eta 0:00:03
     -------------------------------------  561.5/568.0 MB 3.1 MB/s eta 0:00:03
     -------------------------------------  562.0/568.0 MB 3.1 MB/s eta 0:00:02
     -------------------------------------  562.3/568.0 MB 3.1 MB/s eta 0:00:02
     -------------------------------------  563.1/568.0 MB 3.0 MB/s eta 0:00:02
     -------------------------------------  564.1/568.0 MB 3.0 MB/s eta 0:00:02
     -------------------------------------  564.7/568.0 MB 3.0 MB/s eta 0:00:02
     -------------------------------------  565.7/568.0 MB 3.0 MB/s eta 0:00:01
     -------------------------------------  566.8/568.0 MB 3.0 MB/s eta 0:00:01
     -------------------------------------  567.5/568.0 MB 3.0 MB/s eta 0:00:01
     -------------------------------------  567.8/568.0 MB 3.0 MB/s eta 0:00:01
     -------------------------------------  567.8/568.0 MB 3.0 MB/s eta 0:00:01
     -------------------------------------  567.8/568.0 MB 3.0 MB/s eta 0:00:01
     -------------------------------------- 568.0/568.0 MB 3.0 MB/s eta 0:00:00
✔ Download and installation successful
You can now load the package via spacy.load('es_core_news_lg')
[notice] A new release of pip is available: 25.1 -> 25.2
[notice] To update, run: python.exe -m pip install --upgrade pip
In [87]:
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import seaborn as sns
import nltk
import re
import os
import string
import spacy
import math
import networkx as nx
import unicodedata
import unidecode


from nltk.tokenize import word_tokenize, sent_tokenize
from nltk.corpus import stopwords
from nltk.util import ngrams
from collections import Counter
from wordcloud import WordCloud
from itertools import combinations
from scipy import stats
from collections import defaultdict
from flair.data import Sentence
from flair.models import SequenceTagger
from collections import Counter
from sklearn.feature_extraction.text import TfidfVectorizer
from transformers import pipeline
from textstat import textstat
In [3]:
nltk.download('stopwords')
spanish_stopwords = stopwords.words('spanish')
[nltk_data] Downloading package stopwords to
[nltk_data]     C:\Users\guill\AppData\Roaming\nltk_data...
[nltk_data]   Package stopwords is already up-to-date!

Paso 2: Cargar Datos¶

In [4]:
df = pd.read_excel("../datasets/exploration_datasets/silver/silver_df_news.xlsx")
In [5]:
df = pd.read_json("../datasets/exploration_datasets/silver/silver_df_news.json", lines=True)
In [6]:
df.head()
Out[6]:
title category content country newspaper content_unicode content_removed_unwanted_elements content_lowercase content_sentence_tokenization content_lowercase_with_no_punctuation content_without_stopwords content_tokens content_lemmes
0 Hamás pide un intercambio “inmediato” de rehen... internacional Israel continúa con intensos bombardeos en con... Mexico Animal Politico Israel continúa con intensos bombardeos en co... Israel continua con intensos bombardeos en con... israel continua con intensos bombardeos en con... [israel continua con intensos bombardeos en co... israel continua con intensos bombardeos en con... israel continua intensos bombardeos civiles fr... israel continua intensos bombardeos civiles fr... [israel, continuo, intenso, bombardeo, civil, ...
1 Ali Akbar, el último vendedor de periódicos am... internacional "¡Mientras tenga energía, seguiré, trabajaré h... Mexico Animal Politico "¡Mientras tenga energía, seguiré, trabajare... "¡Mientras tenga energia, seguire, trabajare h... "¡mientras tenga energia, seguire, trabajare h... ["¡mientras tenga energia, seguire, trabajare ... ¡mientras tenga energia seguire trabajare hast... ¡ energia seguire trabajare muerte bromea vend... ¡ energia seguire trabajare muerte bromea vend... [energia, seguire, trabajare, muerte, bromear,...
2 Cancillería revisa rutas para el “retorno segu... internacional La Embajada de México en Israel se mantiene en... Mexico Animal Politico La Embajada de México en Israel se mantiene e... La Embajada de Mexico en Israel se mantiene en... la embajada de mexico en israel se mantiene en... [la embajada de mexico en israel se mantiene e... la embajada de mexico en israel se mantiene en... embajada mexico israel mantiene contacto autor... embajada mexico israel mantiene contacto autor... [embajada, mexico, israel, mantener, contacto,...
3 “Nos trataron como animales”, activistas depor... internacional Israel detuvo a más de 400 personas y deportó ... Mexico Animal Politico Israel detuvo a más de 400 personas y deporto... Israel detuvo a mas de 400 personas y deporto ... israel detuvo a mas de 400 personas y deporto ... [israel detuvo a mas de 400 personas y deporto... israel detuvo a mas de 400 personas y deporto ... israel detuvo 400 personas deporto detenidos v... israel detuvo 400 personas deporto detenidos v... [israel, detener, persona, deporto, detenido, ...
4 “Estamos más cerca que nunca de una paz durade... internacional La propuesta de paz de Trump contempla, entre ... Mexico Animal Politico La propuesta de paz de Trump contempla, entre ... La propuesta de paz de Trump contempla, entre ... la propuesta de paz de trump contempla, entre ... [la propuesta de paz de trump contempla, entre... la propuesta de paz de trump contempla entre o... propuesta paz trump contempla puntos alto fueg... propuesta paz trump contempla puntos alto fueg... [propuesta, paz, trump, contemplar, punto, alt...
In [7]:
df.tail()
Out[7]:
title category content country newspaper content_unicode content_removed_unwanted_elements content_lowercase content_sentence_tokenization content_lowercase_with_no_punctuation content_without_stopwords content_tokens content_lemmes
5630 Aumenta a 31 cifra de muertos tras masacre en ... seguridad Guayaquil.- La cifra de reclusos muertos en la... Mexico El Universal Guayaquil.- La cifra de reclusos muertos en la... Guayaquil.- La cifra de reclusos muertos en la... guayaquil.- la cifra de reclusos muertos en la... [guayaquil.- la cifra de reclusos muertos en l... guayaquil la cifra de reclusos muertos en la p... guayaquil cifra reclusos muertos penitenciaria... guayaquil cifra reclusos muertos penitenciaria... [guayaquil, cifra, recluso, muerto, penitencia...
5631 Cataluña, clave para definir quién será el pre... politica El desenlace de las elecciones generales del d... Mexico El Universal El desenlace de las elecciones generales del d... El desenlace de las elecciones generales del d... el desenlace de las elecciones generales del d... [el desenlace de las elecciones generales del ... el desenlace de las elecciones generales del d... desenlace elecciones generales domingo espana ... desenlace elecciones generales domingo espana ... [desenlacir, elección, general, domingo, espán...
5632 Migrantes mexicanos caen del muro fronterizo e... salud Los Ángeles.- La Oficina de Aduanas y Protecci... Mexico El Universal Los Ángeles.- La Oficina de Aduanas y Protecc... Los Angeles.- La Oficina de Aduanas y Protecci... los angeles.- la oficina de aduanas y protecci... [los angeles.- la oficina de aduanas y protecc... los angeles la oficina de aduanas y proteccion... angeles oficina aduanas proteccion fronteriza ... angeles oficina aduanas proteccion fronteriza ... [angel, oficina, aduanas, proteccion, fronteri...
5633 Gobernador de Oaxaca censura a la prensa; fue ... politica El gobernador de Oaxaca y presidente de la Con... Mexico El Universal El gobernador de Oaxaca y presidente de la Con... El gobernador de Oaxaca y presidente de la Con... el gobernador de oaxaca y presidente de la con... [el gobernador de oaxaca y presidente de la co... el gobernador de oaxaca y presidente de la con... gobernador oaxaca presidente conferencia nacio... gobernador oaxaca presidente conferencia nacio... [gobernador, oaxaco, presidente, conferencia, ...
5634 Desabasto y desaparición de las NOMs salud Irene Tello Arista\nCuando decidimos en Impuni... Mexico El Universal Irene Tello Arista\nCuando decidimos en Impuni... Irene Tello Arista Cuando decidimos en Impunid... irene tello arista cuando decidimos en impunid... [irene tello arista cuando decidimos en impuni... irene tello arista cuando decidimos en impunid... irene tello arista decidimos impunidad cero ju... irene tello arista decidimos impunidad cero ju... [irenir, tello, arista, decidir, impunidad, ce...

Paso 3: Identificar las variedades lingüísticas por ubicación geográfica, prensa escrita y tipo de sección¶

Paso 3.1: Distribución y estructura general¶

Analizar la composición del corpus por país, periódico y sección para entender el equilibrio de los datos y la representatividad de cada fuente.

Paso 3.1.1: ¿Cuántas noticias hay por país (Colombia vs. México)?¶

In [8]:
df['country'].value_counts()
Out[8]:
country
Colombia    3980
Mexico      1655
Name: count, dtype: int64
In [9]:
sns.countplot(data=df, x='country')
plt.title("Número de noticias por país")
plt.xlabel("País")
plt.ylabel("Cantidad de noticias")
plt.show()
No description has been provided for this image

Paso 3.1.2: ¿Cuáles son los periódicos con mayor número de textos en cada país?¶

In [10]:
df.groupby(['country', 'newspaper']).size().reset_index(name='num_texts').sort_values(['country', 'num_texts'], ascending=[True, False])
Out[10]:
country newspaper num_texts
3 Colombia Semana 1610
1 Colombia La El Colombiano 1083
2 Colombia La El Espectador 767
0 Colombia El Tiempo 520
5 Mexico El Excelsior 656
4 Mexico Animal Politico 574
7 Mexico La Jornada 392
6 Mexico El Universal 33
In [11]:
# --- Agrupar los datos: número de textos por periódico y país ---
newspaper_counts = (
    df.groupby(['country', 'newspaper'])
      .size()
      .reset_index(name='num_texts')
)

# --- Ordenar de mayor a menor dentro de cada país ---
newspaper_counts = newspaper_counts.sort_values(['country', 'num_texts'], ascending=[True, False])

# --- Crear gráfico por país con el mismo eje X ---
g = sns.FacetGrid(
    newspaper_counts,
    col='country',
    sharex=True,   # 👉 mantiene el mismo rango del eje X
    sharey=False,
    height=5,
    aspect=1
)

g.map_dataframe(
    sns.barplot,
    x='num_texts',
    y='newspaper',
    hue='newspaper',       # ✅ añade hue
    palette='muted',       # ✅ ahora es válido
    legend=False           # ✅ evita mostrar la leyenda
)

# --- Personalización del gráfico ---
g.set_titles(col_template='{col_name}')
g.set_axis_labels('Número de textos', 'Periódico')
g.fig.subplots_adjust(top=0.85)
g.fig.suptitle('Periódicos con mayor número de textos por país', fontsize=14)

# --- Mostrar gráfico ---
plt.show()
No description has been provided for this image

Paso 3.1.3: ¿Qué proporción de textos hay por tipo de sección?¶

In [12]:
df['category'].value_counts(normalize=True)
Out[12]:
category
cultura          0.231588
politica         0.224135
economica        0.214729
internacional    0.178350
salud            0.107010
judicial         0.036912
seguridad        0.007276
Name: proportion, dtype: float64
In [13]:
category_counts = df['category'].value_counts(normalize=True).reset_index()
category_counts.columns = ['category', 'percentage']
category_counts['percentage'] *= 100

plt.figure(figsize=(8,5))
sns.barplot(
    data=category_counts,
    x='percentage',
    y='category',
    hue='category',
    palette='viridis',
    legend=False
)
plt.title('Proporción de textos por tipo de sección')
plt.xlabel('Porcentaje (%)')
plt.ylabel('Sección')
plt.show()
No description has been provided for this image

Paso 3.1.4: ¿Qué longitud promedio tienen los textos (en palabras o oraciones) por país, periódico y sección?¶

In [14]:
df["num_sentences"] = df["content_sentence_tokenization"].apply(len)
df["num_words"] = df["content_tokens"].apply(lambda x: len(x.split()))
In [15]:
df["content_tokens"]
Out[15]:
0       israel continua intensos bombardeos civiles fr...
1       ¡ energia seguire trabajare muerte bromea vend...
2       embajada mexico israel mantiene contacto autor...
3       israel detuvo 400 personas deporto detenidos v...
4       propuesta paz trump contempla puntos alto fueg...
                              ...                        
5630    guayaquil cifra reclusos muertos penitenciaria...
5631    desenlace elecciones generales domingo espana ...
5632    angeles oficina aduanas proteccion fronteriza ...
5633    gobernador oaxaca presidente conferencia nacio...
5634    irene tello arista decidimos impunidad cero ju...
Name: content_tokens, Length: 5635, dtype: object
In [16]:
# Create the grouped dataframe
avg_length = (
    df.groupby(["country", "newspaper", "category"])
    .agg(
        avg_words=("num_words", "mean"),
        avg_sentences=("num_sentences", "mean")
    )
    .reset_index()
)

# Separate and print by country
for country, group in avg_length.groupby("country"):
    print(f"\n=== {country.upper()} ===")
    print(group.sort_values(by="avg_words", ascending=False).to_string(index=False))
=== COLOMBIA ===
 country        newspaper      category  avg_words  avg_sentences
Colombia           Semana       cultura 441.795804      40.593007
Colombia La El Colombiano      politica 364.739583      31.666667
Colombia        El Tiempo     seguridad 358.142857      31.571429
Colombia La El Colombiano     economica 333.381089      30.131805
Colombia La El Colombiano       cultura 323.979798      28.469697
Colombia           Semana         salud 306.918919      24.344595
Colombia La El Colombiano         salud 300.978261      22.880435
Colombia        El Tiempo      politica 297.834862      25.770642
Colombia La El Colombiano internacional 276.839080      21.410920
Colombia        El Tiempo     economica 275.163462      20.817308
Colombia        El Tiempo       cultura 260.064516      24.838710
Colombia           Semana     economica 253.826667      20.244444
Colombia           Semana      politica 251.074074      22.727273
Colombia        El Tiempo         salud 219.403670      18.788991
Colombia        El Tiempo internacional 186.979592      14.438776
Colombia La El Espectador       cultura 109.035714       8.633929
Colombia La El Espectador      judicial  99.910714       7.482143
Colombia La El Espectador         salud  91.201754       7.973684
Colombia La El Espectador      politica  90.056604       7.971698
Colombia La El Espectador internacional  83.590909       6.563636
Colombia La El Espectador     economica  75.158879       7.196262

=== MEXICO ===
country       newspaper      category   avg_words  avg_sentences
 Mexico Animal Politico         salud 1133.388889      93.277778
 Mexico Animal Politico internacional 1060.784615      97.750000
 Mexico Animal Politico     seguridad 1013.944444      85.333333
 Mexico Animal Politico      politica  365.869231      23.588462
 Mexico      La Jornada internacional  266.814433      21.886598
 Mexico    El Universal      politica  219.571429      15.428571
 Mexico      La Jornada      politica  209.545455      15.656566
 Mexico    El Universal         salud  196.000000      13.500000
 Mexico      La Jornada     economica  193.979798      14.171717
 Mexico      La Jornada       cultura  192.731959      14.886598
 Mexico    El Universal     economica  174.666667       9.666667
 Mexico    El Universal     seguridad  149.000000       8.000000
 Mexico    El Universal       cultura  129.500000       9.500000
 Mexico    El Excelsior       cultura  123.931818       9.443182
 Mexico    El Excelsior      judicial  105.072917       7.770833
 Mexico    El Excelsior      politica   95.306011       8.371585
 Mexico    El Excelsior         salud   95.029412       8.147059
 Mexico    El Excelsior internacional   88.097826       6.815217
 Mexico    El Excelsior     economica   77.105263       7.336842
In [17]:
df[(df["country"] == "Mexico") & (df["category"] == "economica")]["content"].iloc[4]
Out[17]:
'Los empresarios del país esperan abrir nuevos mercados en importante feria en Madrid (España) y superar la meta de exportaciones de 2024. . La canasta exportadora de frutas colombianas se presenta con una diversidad que incluye productos tradicionales como el banano, pero también como la piña, el aguacate Hass, la gulupa y los arándanos, entre otras.\nEntre enero y julio de 2025, las exportaciones de frutas frescas colombianas registraron US$1.248 millones, con un aumento de 13,4 % frente al mismo periodo de 2024, de acuerdo con cifras oficiales y el análisis de la Asociación Nacional de Comercio Exterior (Analdex).\nEstados Unidos, Países Bajos, Bélgica, Reino Unido, Alemania e Italia, fueron los destinos que más compraron las frutas colombianas. Allí resalta que EE. UU. tuvo un 27,7 % de participación en el total de estas compras y un aumento de 55,9 %, llegando a US$345,6 millones.'
In [18]:
plt.figure(figsize=(12, 6))

# Compute order by country, then by descending avg_words
order = (
    avg_length
    .sort_values(by=["country", "avg_words"], ascending=[True, False])
    .newspaper
    .unique()
)

sns.barplot(
    data=avg_length,
    x="avg_words",
    y="newspaper",
    hue="country",
    order=order
)

plt.title("Longitud promedio de los textos (en palabras) por país, periódico y sección")
plt.xlabel("Promedio de palabras")
plt.ylabel("Periódico")
plt.legend(title="País")
plt.tight_layout()
plt.show()
No description has been provided for this image
In [19]:
plt.figure(figsize=(12, 6))

# Compute order by country, then by descending avg_words
order = (
    avg_length
    .sort_values(by=["country", "avg_sentences"], ascending=[True, False])
    .newspaper
    .unique()
)

sns.barplot(
    data=avg_length,
    x="avg_sentences",
    y="newspaper",
    hue="country",
    order=order
)

plt.title("Longitud promedio de los textos (en palabras) por país, periódico y sección")
plt.xlabel("Promedio de palabras")
plt.ylabel("Periódico")
plt.legend(title="País")
plt.tight_layout()
plt.show()
No description has been provided for this image

Paso 3.2: Variedades lingüísticas y léxicas¶

Propósito: Identificar las diferencias léxicas y expresiones regionales que caracterizan las variedades del español en ambos países y secciones.

Paso 3.2.1: ¿Qué palabras son exclusivas o más frecuentes en los textos de Colombia frente a los de México?¶

In [20]:
df.head()
Out[20]:
title category content country newspaper content_unicode content_removed_unwanted_elements content_lowercase content_sentence_tokenization content_lowercase_with_no_punctuation content_without_stopwords content_tokens content_lemmes num_sentences num_words
0 Hamás pide un intercambio “inmediato” de rehen... internacional Israel continúa con intensos bombardeos en con... Mexico Animal Politico Israel continúa con intensos bombardeos en co... Israel continua con intensos bombardeos en con... israel continua con intensos bombardeos en con... [israel continua con intensos bombardeos en co... israel continua con intensos bombardeos en con... israel continua intensos bombardeos civiles fr... israel continua intensos bombardeos civiles fr... [israel, continuo, intenso, bombardeo, civil, ... 87 988
1 Ali Akbar, el último vendedor de periódicos am... internacional "¡Mientras tenga energía, seguiré, trabajaré h... Mexico Animal Politico "¡Mientras tenga energía, seguiré, trabajare... "¡Mientras tenga energia, seguire, trabajare h... "¡mientras tenga energia, seguire, trabajare h... ["¡mientras tenga energia, seguire, trabajare ... ¡mientras tenga energia seguire trabajare hast... ¡ energia seguire trabajare muerte bromea vend... ¡ energia seguire trabajare muerte bromea vend... [energia, seguire, trabajare, muerte, bromear,... 146 1549
2 Cancillería revisa rutas para el “retorno segu... internacional La Embajada de México en Israel se mantiene en... Mexico Animal Politico La Embajada de México en Israel se mantiene e... La Embajada de Mexico en Israel se mantiene en... la embajada de mexico en israel se mantiene en... [la embajada de mexico en israel se mantiene e... la embajada de mexico en israel se mantiene en... embajada mexico israel mantiene contacto autor... embajada mexico israel mantiene contacto autor... [embajada, mexico, israel, mantener, contacto,... 102 1342
3 “Nos trataron como animales”, activistas depor... internacional Israel detuvo a más de 400 personas y deportó ... Mexico Animal Politico Israel detuvo a más de 400 personas y deporto... Israel detuvo a mas de 400 personas y deporto ... israel detuvo a mas de 400 personas y deporto ... [israel detuvo a mas de 400 personas y deporto... israel detuvo a mas de 400 personas y deporto ... israel detuvo 400 personas deporto detenidos v... israel detuvo 400 personas deporto detenidos v... [israel, detener, persona, deporto, detenido, ... 114 1292
4 “Estamos más cerca que nunca de una paz durade... internacional La propuesta de paz de Trump contempla, entre ... Mexico Animal Politico La propuesta de paz de Trump contempla, entre ... La propuesta de paz de Trump contempla, entre ... la propuesta de paz de trump contempla, entre ... [la propuesta de paz de trump contempla, entre... la propuesta de paz de trump contempla entre o... propuesta paz trump contempla puntos alto fueg... propuesta paz trump contempla puntos alto fueg... [propuesta, paz, trump, contemplar, punto, alt... 44 510
In [21]:
# Normalize country names
df["country"] = df["country"].str.strip().str.capitalize()
df["country"] = df["country"].replace({"Mexico": "México"})
In [22]:
# Prepare texts by country
corpora = {}
for country in df["country"].unique():
    corpora[country] = [
        " ".join(lemmas) for lemmas in df[df["country"] == country]["content_lemmes"].tolist() if isinstance(lemmas, list)
    ]
In [23]:
# Compute TF-IDF separately for each country
top_words_list = []
for country, texts in corpora.items():
    vectorizer = TfidfVectorizer(stop_words=spanish_stopwords, max_features=10000)
    tfidf = vectorizer.fit_transform(texts)
    tfidf_df = pd.DataFrame(tfidf.toarray(), columns=vectorizer.get_feature_names_out())

    # Average TF-IDF per word
    mean_tfidf = tfidf_df.mean().sort_values(ascending=False).head(10)

    # Save for plotting
    top_words = pd.DataFrame({
        "word": mean_tfidf.index,
        "tfidf": mean_tfidf.values,
        "country": country
    })
    top_words_list.append(top_words)
In [24]:
# Combine results
top_words_df = pd.concat(top_words_list, ignore_index=True)
In [25]:
# Plot side-by-side by country
g = sns.catplot(
    data=top_words_df,
    x="tfidf", y="word",
    col="country",
    kind="bar",
    sharex=False, sharey=False,
    col_wrap=2,
    height=5, aspect=1
)
g.set_titles("{col_name}")
g.set_axis_labels("Valor medio TF-IDF", "Palabra")
g.fig.suptitle("Palabras más distintivas por país (TF-IDF separado por país)", y=1.05)
plt.show()
No description has been provided for this image

Por pais y categoria¶

In [26]:
# Normalize columns
df["country"] = df["country"].str.strip().str.capitalize()
df["country"] = df["country"].replace({"Mexico": "México"})
df["category"] = df["category"].str.strip().str.capitalize()
In [27]:
# Prepare a list to store results
results = []

# Compute TF-IDF per (country, category)
for (country, category), subset in df.groupby(["country", "category"]):
    # Join lemmas into strings
    texts = [
        " ".join(lemmas) for lemmas in subset["content_lemmes"].dropna().tolist() if isinstance(lemmas, list)
    ]

    if len(texts) == 0:
        continue

    # TF-IDF Vectorization
    vectorizer = TfidfVectorizer(stop_words=spanish_stopwords, max_features=10000)
    tfidf = vectorizer.fit_transform(texts)
    tfidf_df = pd.DataFrame(tfidf.toarray(), columns=vectorizer.get_feature_names_out())

    # Average TF-IDF per word
    mean_tfidf = tfidf_df.mean().sort_values(ascending=False).head(10)

    temp = pd.DataFrame({
        "word": mean_tfidf.index,
        "tfidf": mean_tfidf.values,
        "country": country,
        "category": category
    })
    results.append(temp)
In [28]:
# Combine all
top_words_df = pd.concat(results, ignore_index=True)
In [29]:
# Plot: each column = country, each row = category
g = sns.catplot(
    data=top_words_df,
    x="tfidf", y="word",
    col="country",
    row="category",
    kind="bar",
    sharex=False, sharey=False,
    height=4, aspect=1.2
)
g.set_titles("{row_name} | {col_name}")
g.set_axis_labels("Valor medio TF-IDF", "Palabra")
g.fig.suptitle("Palabras más distintivas por país y categoría (TF-IDF separado por grupo)", y=1.02)
plt.show()
No description has been provided for this image

Paso 3.2.2: ¿Existen diferencias en el uso de pronombres, diminutivos o modismos entre ambos países?¶

Análisis de pronombres¶
In [30]:
# Lista básica de pronombres en español
pronouns = set([
    "yo", "tú", "vos", "usted", "él", "ella", "nosotros", "nosotras",
    "ustedes", "ellos", "ellas", "me", "te", "se", "nos", "os", "lo",
    "la", "le", "los", "las", "les", "mí", "ti", "sí", "mío", "mía",
    "suyo", "suya", "nuestro", "nuestra", "este", "ese", "aquel", "esto",
    "eso", "aquello"
])

results_pronouns = []

for country, subset in df.groupby("country"):
    tokens = [lemma for text in subset["content_lemmes"].dropna() for lemma in text]
    pronoun_count = sum(1 for w in tokens if w.lower() in pronouns)
    total_tokens = len(tokens)
    results_pronouns.append({
        "country": country,
        "pronoun_ratio": pronoun_count / total_tokens
    })

pronoun_df = pd.DataFrame(results_pronouns)
sns.barplot(data=pronoun_df, x="country", y="pronoun_ratio")
plt.title("Proporción de pronombres en textos periodísticos")
plt.ylabel("Proporción de pronombres")
plt.show()
No description has been provided for this image
Análisis de diminutivos¶
In [31]:
import re

results_diminutives = []

for country, subset in df.groupby("country"):
    tokens = [lemma for text in subset["content_lemmes"].dropna() for lemma in text]
    diminutives = [w for w in tokens if re.search(r"(ito|ita|cito|cita)$", w.lower())]
    results_diminutives.append({
        "country": country,
        "diminutive_ratio": len(diminutives) / len(tokens)
    })

diminutive_df = pd.DataFrame(results_diminutives)
sns.barplot(data=diminutive_df, x="country", y="diminutive_ratio")
plt.title("Proporción de diminutivos en textos periodísticos")
plt.ylabel("Proporción de diminutivos")
plt.show()
No description has been provided for this image
Análisis de modismos¶
In [32]:
modismos_colombia = {"parche", "pelao", "vaina", "bacano", "chino"}
modismos_mexico = {"chido", "cuate", "lana", "padre", "güey"}

results_modismos = []

for country, subset in df.groupby("country"):
    tokens = [lemma.lower() for text in subset["content_lemmes"].dropna() for lemma in text]
    if country.lower() == "colombia":
        modismos = modismos_colombia
    elif country.lower() == "mexico":
        modismos = modismos_mexico
    else:
        continue

    count_modismos = sum(1 for w in tokens if w in modismos)
    results_modismos.append({
        "country": country,
        "modismo_ratio": count_modismos / len(tokens)
    })

modismo_df = pd.DataFrame(results_modismos)
sns.barplot(data=modismo_df, x="country", y="modismo_ratio")
plt.title("Proporción de modismos en textos periodísticos")
plt.ylabel("Proporción de modismos")
plt.show()
No description has been provided for this image

Paso 3.2.3: ¿Qué términos o expresiones regionales aparecen con mayor frecuencia por sección?¶

In [33]:
# Crear una estructura para guardar las palabras más comunes por país y categoría
regional_terms = {}

for country in df["country"].unique():
    regional_terms[country] = {}
    country_df = df[df["country"] == country]

    for category in country_df["category"].unique():
        # Unir todas las listas de lemas de esa categoría
        all_lemmes = sum(country_df[country_df["category"] == category]["content_lemmes"].tolist(), [])

        # Contar frecuencia
        counter = Counter(all_lemmes)

        # Seleccionar las más frecuentes (ajusta n según tu preferencia)
        most_common = counter.most_common(20)

        regional_terms[country][category] = pd.DataFrame(most_common, columns=["term", "frequency"])
In [34]:
regional_terms
Out[34]:
{'México': {'Internacional':               term  frequency
  0            trump       2068
  1           israel       1794
  2             gaza       1631
  3              ano       1550
  4          israeli       1514
  5          persona       1261
  6         gobierno       1229
  7              bbc       1182
  8        palestino       1135
  9           guerra       1027
  10          ciudad        999
  11           poder        947
  12           mundo        937
  13            hama        936
  14       netanyahu        877
  15      presidente        821
  16        colageno        819
  17           unido        800
  18         rodilla        769
  19  estadounidense        768,
  'Politica':           term  frequency
  0       mexico        925
  1     gobierno        692
  2    sheinbaum        629
  3     judicial        593
  4          mil        556
  5      reforma        546
  6     nacional        530
  7          ano        521
  8     politico        520
  9         foto        514
  10  presidente        491
  11  secretario        489
  12       lopez        469
  13  presidenta        459
  14       corte        458
  15     persona        452
  16    diputado        417
  17   seguridad        413
  18      millón        405
  19    ministro        403,
  'Salud':            term  frequency
  0         salud        468
  1           ano        334
  2       persona        280
  3        millón        239
  4           mil        211
  5      gobierno        198
  6        medico        173
  7        ciudad        165
  8          caso        163
  9          gaza        157
  10  medicamento        156
  11   enfermedad        148
  12    palestino        148
  13        mundo        137
  14     hospital        137
  15          bbc        135
  16        getty        129
  17         peso        127
  18     paciente        127
  19    autoridad        125,
  'Seguridad':          term  frequency
  0      mexico        148
  1      cartel        114
  2    gobierno        110
  3   seguridad        104
  4     persona        101
  5         ano         89
  6    bermudez         86
  7    nacional         62
  8         bbc         60
  9       mundo         57
  10      getty         57
  11       imag         57
  12      poder         57
  13     hernar         55
  14   paraguay         54
  15      grupo         53
  16      unido         53
  17     ciudad         53
  18  autoridad         52
  19      gente         52,
  'Judicial':            term  frequency
  0      nacional         72
  1       militar         58
  2     seguridad         53
  3         petro         50
  4     autoridad         49
  5         corte         47
  6           ano         44
  7      ejercito         43
  8         grupo         39
  9           paz         38
  10     justicia         37
  11      victima         37
  12       armado         37
  13  informacion         35
  14      general         35
  15     fiscalia         35
  16     presunto         35
  17   presidente         35
  18       hombre         34
  19       ataque         33,
  'Cultura':         term  frequency
  0    cultura        184
  1        ano        174
  2     mexico        140
  3   cultural        134
  4   nacional        131
  5       obra        106
  6     ciudad        100
  7        mil         91
  8      libro         87
  9       pais         83
  10     mujer         80
  11      arte         78
  12  mexicano         77
  13      vida         75
  14  historia         69
  15  proyecto         68
  16   artista         67
  17    social         67
  18  gobierno         64
  19    millón         60,
  'Economica':          term  frequency
  0      ciento        497
  1      mexico        338
  2         ano        222
  3      millón        217
  4         mil        180
  5       dolar        166
  6       unido        160
  7     arancel        140
  8       trump        139
  9    mexicano        138
  10  comercial        133
  11   nacional        112
  12       pais        108
  13     unidos        102
  14        mes         94
  15  economico         94
  16    mercado         89
  17       tasa         86
  18   economia         86
  19   comercio         85},
 'Colombia': {'Politica':           term  frequency
  0        petro       1489
  1   presidente       1222
  2     gobierno       1084
  3     politico        885
  4     colombia        758
  5      gustavo        711
  6   colombiano        591
  7     ministro        551
  8          ano        493
  9       semana        478
  10        pais        473
  11    nacional        446
  12         paz        403
  13     persona        398
  14     octubre        396
  15     proceso        377
  16       salud        377
  17   benedetti        368
  18     partido        363
  19   seguridad        350,
  'Salud':            term  frequency
  0         salud       1793
  1           eps        574
  2           ano        564
  3    enfermedad        537
  4      paciente        487
  5       persona        467
  6       sistema        466
  7        riesgo        390
  8          caso        389
  9   medicamento        377
  10       medico        336
  11     atencion        328
  12      estudio        321
  13     colombia        317
  14         vida        317
  15     servicio        310
  16       millón        284
  17     gobierno        271
  18      entidad        251
  19     nacional        246,
  'Economica':            term  frequency
  0           ano       1744
  1        millón       1395
  2      colombia       1380
  3       empresa       1302
  4    colombiano       1094
  5       mercado       1069
  6        sector       1027
  7          pais        993
  8      nacional        908
  9       persona        793
  10       precio        792
  11     servicio        709
  12     producto        681
  13       buscar        660
  14   financiero        659
  15      proceso        639
  16       empleo        593
  17  experiencia        592
  18        dolar        591
  19     gobierno        584,
  'Cultura':           term  frequency
  0          ano       2360
  1     historia       1620
  2        libro       1593
  3         vida       1440
  4        mundo       1324
  5         obra       1192
  6       musico       1145
  7     colombia       1086
  8     pelicula       1060
  9     festival       1054
  10        cine       1031
  11  colombiano        990
  12      semana        975
  13     artista        950
  14      tiempo        934
  15     publico        869
  16     persona        843
  17        arte        830
  18      ciudad        824
  19       lugar        815,
  'Internacional':               term  frequency
  0            trump        940
  1           israel        679
  2       presidente        648
  3              ano        563
  4            unido        528
  5         gobierno        523
  6             gaza        489
  7   estadounidense        485
  8        venezuela        482
  9          israeli        475
  10          maduro        446
  11          ataque        418
  12      venezolano        417
  13            hama        391
  14          guerra        384
  15         persona        378
  16          unidos        337
  17            pais        334
  18         militar        327
  19      colombiano        322,
  'Judicial':            term  frequency
  0      nacional         78
  1       militar         58
  2     seguridad         53
  3         petro         53
  4         corte         53
  5           ano         51
  6      justicia         51
  7     autoridad         50
  8      ejercito         43
  9       victima         43
  10         caso         42
  11       delito         42
  12        grupo         41
  13          paz         41
  14     fiscalia         40
  15        penal         39
  16       armado         38
  17      general         36
  18   presidente         36
  19  informacion         35,
  'Seguridad':              term  frequency
  0       conflicto         27
  1         persona         22
  2             ano         22
  3           grupo         17
  4          guerra         13
  5         tensión         13
  6          region         13
  7            arma         12
  8          armado         10
  9          tiempo         10
  10        armenia         10
  11        control          9
  12      seguridad          9
  13      incidente          9
  14  independencia          9
  15         pueblo          9
  16        ecuador          9
  17        quimica          9
  18    territorial          8
  19            paz          8}}
In [35]:
# Ejemplo: ver los términos más comunes de "Mexico" en la categoría "Economía"
regional_terms["México"]["Economica"].head(10)
Out[35]:
term frequency
0 ciento 497
1 mexico 338
2 ano 222
3 millón 217
4 mil 180
5 dolar 166
6 unido 160
7 arancel 140
8 trump 139
9 mexicano 138
In [36]:
import seaborn as sns
import matplotlib.pyplot as plt
import pandas as pd

# Combine results into one DataFrame
plot_df_all = []
for country in regional_terms.keys():
    for category in regional_terms[country].keys():
        temp_df = regional_terms[country][category].copy()
        temp_df["country"] = country
        temp_df["category"] = category
        plot_df_all.append(temp_df)

plot_df_all = pd.concat(plot_df_all)

# Keep top 10 most frequent terms per (country, category)
plot_df_all = (
    plot_df_all.groupby(["country", "category"], group_keys=False, observed=True)
    .apply(lambda x: x.nlargest(10, "frequency"))
    .reset_index(drop=True)
)


# Create one grid of plots: columns = countries, rows = categories
g = sns.FacetGrid(
    plot_df_all,
    col="country",
    row="category",
    sharex=False,
    sharey=False,
    height=4,
    aspect=1.2
)

# Add barplots to each facet
g.map_dataframe(
    sns.barplot,
    x="frequency",
    y="term",
    color="skyblue"
)

# Adjust layout and titles
g.set_titles(row_template="{row_name}", col_template="{col_name}")
g.set_axis_labels("Frecuencia", "Término")
plt.subplots_adjust(top=0.93)
g.fig.suptitle("Términos más frecuentes por país y categoría", fontsize=14)
plt.show()
C:\Users\guill\AppData\Local\Temp\ipykernel_59120\3206556652.py:19: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
  .apply(lambda x: x.nlargest(10, "frequency"))
No description has been provided for this image

Paso 3.2.4: ¿Qué diferencias hay en las palabras más frecuentes por periódico dentro del mismo país?¶

In [37]:
results = []

# Calcular TF-IDF por (país, periódico)
for (country, newspaper), subset in df.groupby(["country", "newspaper"]):
    texts = subset["content_lemmes"].dropna().tolist()
    if len(texts) == 0:
        continue

    # Unir los lemas en cadenas de texto
    texts = [" ".join(t) for t in texts]

    vectorizer = TfidfVectorizer(stop_words=spanish_stopwords, max_features=10000)
    tfidf = vectorizer.fit_transform(texts)
    tfidf_df = pd.DataFrame(tfidf.toarray(), columns=vectorizer.get_feature_names_out())

    mean_tfidf = tfidf_df.mean().sort_values(ascending=False).head(10)

    temp = pd.DataFrame({
        "word": mean_tfidf.index,
        "tfidf": mean_tfidf.values,
        "country": country,
        "newspaper": newspaper
    })
    results.append(temp)

freq_df = pd.concat(results, ignore_index=True)

# Seleccionar las 10 palabras más relevantes por periódico
freq_df_top = (
    freq_df.groupby(["country", "newspaper"], group_keys=False)
    .apply(lambda x: x.nlargest(10, "tfidf"))
    .reset_index(drop=True)
)

# Increase figure size and layout
g = sns.catplot(
    data=freq_df_top,
    x="tfidf", y="word",
    hue="newspaper",
    col="country",
    kind="bar",
    dodge=True,
    sharex=False,
    height=6, aspect=1.3,
    col_wrap=1  # ✅ One country per row for clarity
)

# Improve title and labels
g.set_titles("País: {col_name}")
g.set_axis_labels("TF-IDF promedio", "Palabra")

# Make text smaller and readable
for ax in g.axes.flat:
    ax.tick_params(axis='y', labelsize=8)
    ax.tick_params(axis='x', labelsize=8)
    ax.legend(title="Periódico", fontsize=8)

plt.subplots_adjust(top=0.9)
plt.suptitle("Palabras más frecuentes por periódico dentro de cada país", fontsize=14)
plt.show()
C:\Users\guill\AppData\Local\Temp\ipykernel_59120\3578012932.py:31: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
  .apply(lambda x: x.nlargest(10, "tfidf"))
C:\Users\guill\AppData\Local\Temp\ipykernel_59120\3578012932.py:56: UserWarning: No artists with labels found to put in legend.  Note that artists whose label start with an underscore are ignored when legend() is called with no argument.
  ax.legend(title="Periódico", fontsize=8)
No description has been provided for this image

Paso 3.2.5: ¿Se observan diferencias en el uso de tiempos verbales o estructuras sintácticas entre países?¶

Paso 3.3 Reconocimiento de entidades y temas dominantes (NER + Topic Modeling)¶

Propósito: Detectar las entidades nombradas y los temas principales en las noticias para comprender las diferencias semánticas y discursivas entre países y secciones, así como posibles focos temáticos que influyan en la generación de preguntas y en el rendimiento de los modelos QA.

Paso 3.3.1: ¿Qué entidades nombradas (PERSON, LOC, ORG, DATE, EVENT) aparecen con mayor frecuencia por país y por categoria?¶

Con spaCy¶
In [38]:
nlp = spacy.load("es_core_news_lg")
In [39]:
def extract_entities_spacy(text):
    """Extract (entity_text, entity_label) pairs from text."""
    doc = nlp(text)
    entities = [(ent.text, ent.label_) for ent in doc.ents]
    return entities
In [40]:
df["entities"] = df["content_lemmes"].apply(
    lambda lemmas: extract_entities_spacy(" ".join(lemmas)) if isinstance(lemmas, list) else []
)
In [41]:
# --- 2. Expandir el DataFrame ---
entity_rows = []
for _, row in df.iterrows():
    for ent_text, ent_label in row["entities"]:
        entity_rows.append({
            "country": row["country"],
            "category": row["category"],
            "entity": ent_text.strip(),
            "type": ent_label
        })

entity_df = pd.DataFrame(entity_rows)
In [42]:
# --- 3. Calcular frecuencia de cada entidad ---
entity_freq = (
    entity_df.groupby(["country", "category", "type", "entity"])
    .size()
    .reset_index(name="frequency")
)
In [43]:
# --- 4. Seleccionar las 30 más frecuentes por país y sección ---
top_entities = (
    entity_freq.groupby(["country", "category", "type"], group_keys=False)
    .apply(lambda x: x.nlargest(30, "frequency"))
    .reset_index(drop=True)
)
C:\Users\guill\AppData\Local\Temp\ipykernel_59120\3759326587.py:4: FutureWarning: DataFrameGroupBy.apply operated on the grouping columns. This behavior is deprecated, and in a future version of pandas the grouping columns will be excluded from the operation. Either pass `include_groups=False` to exclude the groupings or explicitly select the grouping columns after groupby to silence this warning.
  .apply(lambda x: x.nlargest(30, "frequency"))
In [44]:
# Define the two countries to compare
countries = ["Colombia", "México"]

# Prepare a figure with two subplots
fig, axes = plt.subplots(1, 2, figsize=(20, 8))

for ax, country in zip(axes, countries):
    # Extract entity texts
    entities_texts = df[df["country"] == country]["entities"].explode().dropna()
    entities_texts = [ent[0] for ent in entities_texts if isinstance(ent, tuple)]

    # Skip if no entities found
    if len(entities_texts) == 0:
        ax.set_title(f"{country} – sin datos")
        ax.axis("off")
        continue

    # Generate word cloud
    text = " ".join(entities_texts)
    wc = WordCloud(
        width=1000,
        height=600,
        background_color="white",
        max_words=100,
        collocations=False
    ).generate(text)

    # Display side-by-side
    ax.imshow(wc, interpolation="bilinear")
    ax.axis("off")
    ax.set_title(f"Entidades nombradas más frecuentes en {country}", fontsize=16)

plt.tight_layout()
plt.show()
No description has been provided for this image
In [45]:
from wordcloud import WordCloud
import matplotlib.pyplot as plt

countries = ["Colombia", "México"]  # adjust if spelling differs

for category in df["category"].unique():
    fig, axes = plt.subplots(1, 2, figsize=(16, 8))
    fig.suptitle(f"Entidades nombradas más frecuentes por país – {category}", fontsize=16)

    for i, country in enumerate(countries):
        subset = df[(df["country"] == country) & (df["category"] == category)]
        entities_texts = subset["entities"].explode().dropna()
        entities_texts = [ent[0] for ent in entities_texts if isinstance(ent, tuple)]

        if len(entities_texts) == 0:
            axes[i].axis("off")
            axes[i].set_title(f"{country} – sin datos")
            continue

        text = " ".join(entities_texts)
        wc = WordCloud(
            width=800, height=600,
            background_color="white",
            max_words=100,
            collocations=False
        ).generate(text)

        axes[i].imshow(wc, interpolation="bilinear")
        axes[i].axis("off")
        axes[i].set_title(f"{country}")

    plt.tight_layout(rect=[0, 0, 1, 0.95])
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [46]:
# Get all unique entity types across both countries
entity_types = sorted(
    set(
        ent[1]
        for ents in df["entities"].dropna().explode()
        if isinstance(ents, tuple)
        for ent in [ents]
    )
)

countries = ["Colombia", "México"]

# Loop over entity types
for entity_type in entity_types:
    fig, axes = plt.subplots(1, 2, figsize=(20, 8))

    for ax, country in zip(axes, countries):
        # Extract entities of this type for each country
        entities_texts = df[df["country"] == country]["entities"].explode().dropna()
        entities_texts = [ent[0] for ent in entities_texts if isinstance(ent, tuple) and ent[1] == entity_type]

        # Skip if none
        if len(entities_texts) == 0:
            ax.set_title(f"{country} – sin datos", fontsize=14)
            ax.axis("off")
            continue

        # Generate WordCloud
        text = " ".join(entities_texts)
        wc = WordCloud(
            width=1000, height=600,
            background_color="white",
            max_words=100,
            collocations=False
        ).generate(text)

        ax.imshow(wc, interpolation="bilinear")
        ax.axis("off")
        ax.set_title(f"{country} – {entity_type}", fontsize=16)

    fig.suptitle(f"Entidades de tipo {entity_type}", fontsize=20, y=1.02)
    plt.tight_layout()
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [47]:
countries = ["Colombia", "México"]
entity_types = sorted(
    set(
        ent[1]
        for ents in df["entities"].dropna().explode()
        if isinstance(ents, tuple)
        for ent in [ents]
    )
)

# Loop over categories
for category in df["category"].unique():
    print(f"=== {category.upper()} ===")

    # One row per entity type, two columns (Colombia vs Mexico)
    fig, axes = plt.subplots(len(entity_types), 2, figsize=(20, 5 * len(entity_types)))

    if len(entity_types) == 1:
        axes = [axes]  # handle case of single entity type

    for i, entity_type in enumerate(entity_types):
        for j, country in enumerate(countries):
            ax = axes[i][j]

            subset = df[(df["country"] == country) & (df["category"] == category)]
            entities_texts = subset["entities"].explode().dropna()
            entities_texts = [ent[0] for ent in entities_texts if isinstance(ent, tuple) and ent[1] == entity_type]

            if len(entities_texts) == 0:
                ax.set_title(f"{country} – {entity_type}: sin datos", fontsize=12)
                ax.axis("off")
                continue

            # Generate WordCloud
            text = " ".join(entities_texts)
            wc = WordCloud(
                width=800, height=400,
                background_color="white",
                max_words=100,
                collocations=False
            ).generate(text)

            ax.imshow(wc, interpolation="bilinear")
            ax.axis("off")
            ax.set_title(f"{country} – {entity_type}", fontsize=14)

    fig.suptitle(f"Entidades nombradas por tipo y país – Categoría: {category}", fontsize=18, y=1.02)
    plt.tight_layout()
    plt.show()
=== INTERNACIONAL ===
No description has been provided for this image
=== POLITICA ===
No description has been provided for this image
=== SALUD ===
No description has been provided for this image
=== SEGURIDAD ===
No description has been provided for this image
=== ECONOMICA ===
No description has been provided for this image
=== CULTURA ===
No description has been provided for this image
=== JUDICIAL ===
No description has been provided for this image

Paso 3.3.2: ¿Qué entidades son exclusivas de cada país o comparten ambos?¶

In [48]:
# Aseguramos que 'entities' tenga formato [(texto, tipo), ...]
df_entities = df.explode("entities").dropna(subset=["entities"])
df_entities["entity_text"] = df_entities["entities"].apply(lambda x: x[0] if isinstance(x, tuple) else None)
df_entities["entity_type"] = df_entities["entities"].apply(lambda x: x[1] if isinstance(x, tuple) else None)

# Agrupamos entidades por país y tipo
entities_by_country = (
    df_entities.groupby(["country", "entity_type"])["entity_text"]
    .apply(lambda x: set(map(str.lower, x.dropna())))
    .reset_index()
)

# Unimos Colombia y México por tipo de entidad
merged = entities_by_country.pivot(index="entity_type", columns="country", values="entity_text")

# Reemplazamos NaN con conjuntos vacíos
merged = merged.applymap(lambda x: x if isinstance(x, set) else set())

# Calculamos exclusivas y compartidas
results = []
for entity_type, row in merged.iterrows():
    colombia_ents = row.get("Colombia", set())
    mexico_ents = row.get("México", set())

    exclusivas_col = colombia_ents - mexico_ents
    exclusivas_mex = mexico_ents - colombia_ents
    compartidas = colombia_ents & mexico_ents

    results.append({
        "entity_type": entity_type,
        "exclusivas_colombia": list(exclusivas_col)[:15],  # top 15
        "exclusivas_mexico": list(exclusivas_mex)[:15],
        "compartidas": list(compartidas)[:15]
    })

exclusive_df = pd.DataFrame(results)
exclusive_df
C:\Users\guill\AppData\Local\Temp\ipykernel_59120\3689353077.py:17: FutureWarning: DataFrame.applymap has been deprecated. Use DataFrame.map instead.
  merged = merged.applymap(lambda x: x if isinstance(x, set) else set())
Out[48]:
entity_type exclusivas_colombia exclusivas_mexico compartidas
0 LOC [fenomeno nino, santa barbara, novgorod, ibaga... [mexico mexico responderar, bebes, san diego c... [mediacion egipto, urgio, brasilén, teatro sca...
1 MISC [boeing poseidon, blue ivy carter, registrir, ... [iran b hama c, embajadoro mexico chile alicia... [uss lake erie, fiscalio momento, cop millón, ...
2 ORG [bogota fashion week internacionalizacion, uni... [movimiento politico unido, partido verde jose... [dfs, unia solidaridad internacional defensa d...
3 PER [gabriel enrique castilla castillo, camilo orb... [gilberto batiz claudia valle aguilasocho, qui... [dea, lena, francoargelín helenir, polvoro bar...
In [49]:
# Compute exclusive/shared entities per country
col_entities = set(
    ent[0].lower() for ent in df[df["country"]=="Colombia"]["entities"].explode()
    if isinstance(ent, tuple)
)
mex_entities = set(
    ent[0].lower() for ent in df[df["country"]=="México"]["entities"].explode()
    if isinstance(ent, tuple)
)

# Prepare DataFrame
plot_df = pd.DataFrame([
    {"country": "Colombia", "Type": "Exclusive", "Count": len(col_entities - mex_entities)},
    {"country": "Colombia", "Type": "Shared", "Count": len(col_entities & mex_entities)},
    {"country": "México", "Type": "Exclusive", "Count": len(mex_entities - col_entities)},
    {"country": "México", "Type": "Shared", "Count": len(col_entities & mex_entities)}
])

# Plot
sns.catplot(
    data=plot_df,
    x="Type", y="Count",
    hue="Type", col="country",
    kind="bar", dodge=False,
    height=5, sharey=True
)
plt.subplots_adjust(top=0.85)
plt.suptitle("Exclusive vs Shared Named Entities per Country")
plt.show()
No description has been provided for this image
In [50]:
# Loop over each entity type
for idx, row in exclusive_df.iterrows():
    entity_type = row["entity_type"]
    colombia_text = " ".join(row["exclusivas_colombia"])
    mexico_text = " ".join(row["exclusivas_mexico"])
    shared_text = " ".join(row["compartidas"])

    # Create a figure with 3 subplots
    fig, axes = plt.subplots(1, 3, figsize=(20, 6))

    # Colombia exclusive
    wc_col = WordCloud(width=600, height=400, background_color="white").generate(colombia_text)
    axes[0].imshow(wc_col, interpolation="bilinear")
    axes[0].axis("off")
    axes[0].set_title(f"Exclusivas Colombia – {entity_type}")

    # Mexico exclusive
    wc_mex = WordCloud(width=600, height=400, background_color="white").generate(mexico_text)
    axes[1].imshow(wc_mex, interpolation="bilinear")
    axes[1].axis("off")
    axes[1].set_title(f"Exclusivas México – {entity_type}")

    # Shared
    wc_shared = WordCloud(width=600, height=400, background_color="white").generate(shared_text)
    axes[2].imshow(wc_shared, interpolation="bilinear")
    axes[2].axis("off")
    axes[2].set_title(f"Compartidas – {entity_type}")

    plt.suptitle(f"Entidades nombradas – {entity_type}", fontsize=16)
    plt.tight_layout()
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
In [51]:
# Extract sets of entities per country
entities_col = set([ent[0] for ents in df[df["country"]=="Colombia"]["entities"] if isinstance(ents, list) for ent in ents])
entities_mex = set([ent[0] for ents in df[df["country"]=="México"]["entities"] if isinstance(ents, list) for ent in ents])

# Entities exclusive to each country
exclusive_col = entities_col - entities_mex
exclusive_mex = entities_mex - entities_col

# Entities shared by both
shared_entities = entities_col & entities_mex

print(f"Exclusive to Colombia: {len(exclusive_col)} entities")
print(f"Exclusive to México: {len(exclusive_mex)} entities")
print(f"Shared: {len(shared_entities)} entities")
Exclusive to Colombia: 26984 entities
Exclusive to México: 7936 entities
Shared: 3250 entities
In [52]:
# Prepare a DataFrame to store counts
rows = []

for category in df["category"].unique():
    col_entities = set(
        [ent[0] for ent in df[(df["country"]=="Colombia") & (df["category"]==category)]["entities"].explode()
         if isinstance(ent, tuple)]
    )
    mex_entities = set(
        [ent[0] for ent in df[(df["country"]=="México") & (df["category"]==category)]["entities"].explode()
         if isinstance(ent, tuple)]
    )
    exclusive_col = col_entities - mex_entities
    exclusive_mex = mex_entities - col_entities
    shared = col_entities & mex_entities

    rows.append({
        "category": category,
        "Exclusive Colombia": len(exclusive_col),
        "Exclusive México": len(exclusive_mex),
        "Shared": len(shared)
    })

plot_df = pd.DataFrame(rows)

# Plot
plot_df.set_index("category").plot(kind="bar", figsize=(12,6))
plt.ylabel("Number of Entities")
plt.title("Exclusive and Shared Named Entities per Category")
plt.xticks(rotation=45)
plt.legend(title="Entity Type")
plt.tight_layout()
plt.show()
No description has been provided for this image
In [53]:
for category in df["category"].unique():
    # Get all entities per country
    col_entities = [ent[0] for ent in df[(df["country"]=="Colombia") & (df["category"]==category)]["entities"].explode()
                    if isinstance(ent, tuple)]
    mex_entities = [ent[0] for ent in df[(df["country"]=="México") & (df["category"]==category)]["entities"].explode()
                    if isinstance(ent, tuple)]

    # Count frequencies
    col_counter = Counter(col_entities)
    mex_counter = Counter(mex_entities)

    # Exclusive and shared entities with frequencies
    exclusive_col = {k:v for k,v in col_counter.items() if k not in mex_counter}
    exclusive_mex = {k:v for k,v in mex_counter.items() if k not in col_counter}
    shared = {k:v for k,v in col_counter.items() if k in mex_counter}

    # Create figure
    fig, axes = plt.subplots(1, 3, figsize=(24,8))
    fig.suptitle(f"Named Entities – Category: {category}", fontsize=18)

    # Word clouds
    for ax, data, title in zip(axes, [exclusive_col, exclusive_mex, shared],
                               ["Exclusive Colombia", "Exclusive México", "Shared"]):
        if data:  # Only generate if there is data
            wc = WordCloud(width=800, height=600, background_color="white", max_words=100, collocations=False)
            wc.generate_from_frequencies(data)
            ax.imshow(wc, interpolation="bilinear")
        ax.axis("off")
        ax.set_title(title, fontsize=14)

    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Paso 3.3.3: ¿Qué temas predominan?¶

In [54]:
# Count texts per category
category_counts = df["category"].value_counts().reset_index()
category_counts.columns = ["category", "count"]

# Plot
plt.figure(figsize=(10,6))
sns.barplot(
    data=category_counts.sort_values("count", ascending=False),
    x="category",
    y="count",
    palette="Set2"
)
plt.title("Número de noticias por categoría")
plt.xlabel("Categoría")
plt.ylabel("Cantidad de noticias")
plt.xticks(rotation=45)
plt.show()
C:\Users\guill\AppData\Local\Temp\ipykernel_59120\719633996.py:7: FutureWarning: 

Passing `palette` without assigning `hue` is deprecated and will be removed in v0.14.0. Assign the `x` variable to `hue` and set `legend=False` for the same effect.

  sns.barplot(
No description has been provided for this image

Paso 3.3.4: ¿Cómo varía la densidad de entidades por categoria o medio?¶

In [55]:
# Ensure content_tokens are lists of words
df["content_tokens"] = df["content_tokens"].apply(lambda x: x.split() if isinstance(x, str) else [])

# Ensure entities column is a list
df["entities"] = df["entities"].apply(lambda x: x if isinstance(x, list) else [])

# Compute number of entities and tokens
df["num_entities"] = df["entities"].apply(len)
df["num_tokens"] = df["content_tokens"].apply(len)
In [56]:
# Compute entity density safely
df["entity_density"] = df.apply(
    lambda row: row["num_entities"] / row["num_tokens"] if row["num_tokens"] > 0 else 0, axis=1
)

# Quick check
df[["num_entities", "num_tokens", "entity_density"]].head()
Out[56]:
num_entities num_tokens entity_density
0 78 988 0.078947
1 89 1549 0.057456
2 154 1342 0.114754
3 113 1292 0.087461
4 52 510 0.101961
In [57]:
plt.figure(figsize=(12,6))
sns.boxplot(data=df, x="category", y="entity_density")
plt.ylabel("Entity Density")
plt.xticks(rotation=45)
plt.title("Distribution of Entity Density by Category")
plt.show()
No description has been provided for this image
In [58]:
plt.figure(figsize=(12,6))
sns.boxplot(data=df, x="newspaper", y="entity_density")
plt.ylabel("Entity Density")
plt.xticks(rotation=45)
plt.title("Distribution of Entity Density by Newspaper")
plt.show()
No description has been provided for this image

Paso 3.3.5: ¿Qué entidades se asocian a los temas más recurrentes (por ejemplo, “ONU” con “Relaciones Internacionales”)?¶

In [59]:
# Make sure entities column is a list of tuples
df["entities"] = df["entities"].apply(lambda x: x if isinstance(x, list) else [])

# Extract only the entity text
df["entity_texts"] = df["entities"].apply(lambda ents: [e[0] for e in ents if isinstance(e, tuple)])
In [60]:
df_exploded = df.explode("entity_texts").dropna(subset=["entity_texts"])
In [61]:
entity_topic_freq = (
    df_exploded.groupby(["category", "entity_texts"])
    .size()
    .reset_index(name="frequency")
    .sort_values(["category", "frequency"], ascending=[True, False])
)
In [62]:
entity_topic_freq = (
    df_exploded.groupby(["category", "entity_texts"])
    .size()
    .reset_index(name="frequency")
    .sort_values(["category", "frequency"], ascending=[True, False])
)
In [63]:
top_entities_per_category = (
    entity_topic_freq.groupby("category")
    .head(10)  # top 10 entities per category
)
top_entities_per_category
Out[63]:
category entity_texts frequency
1890 Cultura bogota 457
3271 Cultura colombia 451
10157 Cultura medellin 225
13940 Cultura semán 199
10365 Cultura mexico 192
... ... ... ...
41823 Seguridad hernar bermudez 21
41597 Seguridad bbc news mundo 18
41745 Seguridad facebook canal 18
42146 Seguridad seguirno youtube 18
41779 Seguridad gabino lezama 14

70 rows × 3 columns

In [64]:
import seaborn as sns
import matplotlib.pyplot as plt

g = sns.catplot(
    data=top_entities_per_category,
    x="frequency",
    y="entity_texts",
    hue="category",
    kind="bar",
    col="category",
    col_wrap=2,
    dodge=False,
    sharex=False,
    height=5
)

plt.subplots_adjust(top=0.9)
plt.suptitle("Entidades más frecuentes asociadas a cada tema")
plt.show()
No description has been provided for this image

Paso 3.4: Análisis morfosintáctico y de estilo¶

Propósito: Detectar diferencias en el estilo periodístico y la estructura gramatical que podrían influir en el tipo de preguntas o respuestas que se pueden generar.

Paso 3.4.1: ¿Qué categorías gramaticales (sustantivos, verbos, adjetivos, etc.) predominan en cada país o sección?¶

In [65]:
def get_pos_tags(text):
    """
    Input: string
    Output: list of POS tags
    """
    doc = nlp(text)
    return [token.pos_ for token in doc]
In [66]:
# Create a new column with POS tags
df["pos_tags"] = df["content"].apply(lambda x: get_pos_tags(x) if isinstance(x, str) else [])
In [67]:
df_pos = df.explode("pos_tags").dropna(subset=["pos_tags"])
In [68]:
pos_counts = (
    df_pos.groupby(["country", "category", "pos_tags"])
    .size()
    .reset_index(name="count")
    .sort_values(["country", "category", "count"], ascending=[True, True, False])
)
pos_counts.head(20)
Out[68]:
country category pos_tags count
7 Colombia Cultura NOUN 190251
1 Colombia Cultura ADP 146372
5 Colombia Cultura DET 132418
12 Colombia Cultura PUNCT 131508
16 Colombia Cultura VERB 90159
11 Colombia Cultura PROPN 82324
0 Colombia Cultura ADJ 68211
10 Colombia Cultura PRON 57616
4 Colombia Cultura CCONJ 36297
2 Colombia Cultura ADV 34045
3 Colombia Cultura AUX 29375
14 Colombia Cultura SPACE 22529
13 Colombia Cultura SCONJ 19821
8 Colombia Cultura NUM 11832
15 Colombia Cultura SYM 1709
6 Colombia Cultura INTJ 623
9 Colombia Cultura PART 430
24 Colombia Economica NOUN 132476
18 Colombia Economica ADP 102011
22 Colombia Economica DET 78974
In [69]:
import seaborn as sns
import matplotlib.pyplot as plt

# Example: POS distribution per country
g = sns.catplot(
    data=pos_counts,
    x="count",
    y="pos_tags",
    hue="country",
    col="category",  # optional, to separate by section
    kind="bar",
    col_wrap=2,
    sharex=False,
    height=5,
    dodge=True
)

plt.subplots_adjust(top=0.9)
plt.suptitle("Distribución de categorías gramaticales por país y sección")
plt.show()
No description has been provided for this image

Paso 3.4.2: ¿Qué tan diversa es la estructura léxica entre secciones (índices de diversidad léxica o entropía)?¶

In [70]:
# If content_tokens are already tokenized
def lexical_diversity(tokens):
    if len(tokens) == 0:
        return 0
    return len(set(tokens)) / len(tokens)

# Apply per text
df["lexical_diversity"] = df["content_tokens"].apply(lambda x: lexical_diversity(x) if isinstance(x, list) else 0)
In [71]:
lex_div_by_category = (
    df.groupby("category")["lexical_diversity"]
    .mean()
    .sort_values(ascending=False)
    .reset_index()
)
lex_div_by_category
Out[71]:
category lexical_diversity
0 Judicial 0.792341
1 Salud 0.758182
2 Cultura 0.754181
3 Economica 0.743021
4 Politica 0.733451
5 Internacional 0.727534
6 Seguridad 0.697251
In [72]:
def shannon_entropy(tokens):
    if not tokens:
        return 0
    counts = np.array(list(Counter(tokens).values()))
    probs = counts / counts.sum()
    return -(probs * np.log2(probs)).sum()

# Apply per text
df["entropy"] = df["content_tokens"].apply(lambda x: shannon_entropy(x) if isinstance(x, list) else 0)
In [73]:
entropy_by_category = (
    df.groupby("category")["entropy"]
    .mean()
    .sort_values(ascending=False)
    .reset_index()
)
entropy_by_category
Out[73]:
category entropy
0 Seguridad 7.742374
1 Cultura 7.273735
2 Internacional 7.209182
3 Economica 6.891542
4 Salud 6.728106
5 Politica 6.705495
6 Judicial 5.979230
In [74]:
plt.figure(figsize=(10,6))
sns.barplot(data=entropy_by_category, x="category", y="entropy", hue="entropy", palette="viridis", legend=False)
plt.xticks(rotation=45)
plt.ylabel("Average Lexical Entropy")
plt.title("Diversidad léxica promedio por sección")
plt.show()
No description has been provided for this image

Paso 3.4.3: ¿Existen patrones de estilo o formalidad distintos por tipo de periódico o país?¶

In [78]:
# Longitud promedio de oraciones
df["avg_sentence_length"] = df["content_sentence_tokenization"].apply(
    lambda sents: np.mean([len(sent.split()) for sent in sents]) if isinstance(sents, list) and len(sents) > 0 else 0
)

# Longitud promedio de palabras
df["avg_word_length"] = df["content_tokens"].apply(
    lambda tokens: np.mean([len(token) for token in tokens]) if isinstance(tokens, list) and len(tokens) > 0 else 0
)
In [79]:
df["flesch_reading_ease"] = df["content_removed_unwanted_elements"].apply(lambda x: textstat.flesch_reading_ease(x) if isinstance(x, str) else 0)
In [80]:
df["lexical_diversity"] = df["content_tokens"].apply(lambda x: len(set(x))/len(x) if len(x) > 0 else 0)
In [81]:
df_grouped = df.groupby(["country", "newspaper"]).agg({
    "flesch_reading_ease": "mean",
    "avg_sentence_length": "mean",
    "lexical_diversity": "mean"
}).reset_index()
In [82]:
fig, axes = plt.subplots(1, 3, figsize=(18,5))

sns.barplot(data=df_grouped, x="newspaper", y="flesch_reading_ease", hue="country", ax=axes[0])
axes[0].set_title("Flesch Reading Ease")
axes[0].set_xticklabels(axes[0].get_xticklabels(), rotation=45)

sns.barplot(data=df_grouped, x="newspaper", y="avg_sentence_length", hue="country", ax=axes[1])
axes[1].set_title("Longitud promedio de oraciones")
axes[1].set_xticklabels(axes[1].get_xticklabels(), rotation=45)

sns.barplot(data=df_grouped, x="newspaper", y="lexical_diversity", hue="country", ax=axes[2])
axes[2].set_title("Diversidad léxica")
axes[2].set_xticklabels(axes[2].get_xticklabels(), rotation=45)

plt.tight_layout()
plt.show()
C:\Users\guill\AppData\Local\Temp\ipykernel_59120\2938499745.py:5: UserWarning: set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator.
  axes[0].set_xticklabels(axes[0].get_xticklabels(), rotation=45)
C:\Users\guill\AppData\Local\Temp\ipykernel_59120\2938499745.py:9: UserWarning: set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator.
  axes[1].set_xticklabels(axes[1].get_xticklabels(), rotation=45)
C:\Users\guill\AppData\Local\Temp\ipykernel_59120\2938499745.py:13: UserWarning: set_ticklabels() should only be used with a fixed number of ticks, i.e. after set_ticks() or using a FixedLocator.
  axes[2].set_xticklabels(axes[2].get_xticklabels(), rotation=45)
No description has been provided for this image

Paso 4: Desarrollar la metodología para generar preguntas y respuestas automáticas¶

Propósito: Evaluar la calidad estructural y semántica de los textos para determinar su potencial en la generación automática de preguntas y respuestas.

Paso 4.1: ¿Qué porcentaje de noticias tiene párrafos con oraciones bien formadas (sin errores o repeticiones)?¶

In [83]:
def is_well_formed_sentence(sent):
    sent = sent.strip()
    if len(sent) == 0:
        return False

    # Remove leading non-letter characters (quotes, parentheses, etc.)
    first_char = re.sub(r'^[^A-Za-zÁÉÍÓÚÑáéíóúñ]+', '', sent)
    if len(first_char) == 0:
        return False

    # Check if starts with uppercase
    if not first_char[0].isupper():
        return False

    # Check if ends with valid punctuation
    if sent[-1] not in ".!?":
        return False

    # Check repeated words
    words = [unidecode.unidecode(w.lower()) for w in sent.split()]
    for i in range(len(words)-1):
        if words[i] == words[i+1]:
            return False

    return True
In [84]:
def is_well_formed_paragraph(paragraph):
    sentences = re.split(r'(?<=[.!?¿¡]) +', paragraph)
    if len(sentences) == 0:
        return False

    # Paragraph is well-formed if all sentences are well-formed
    return all(is_well_formed_sentence(s) for s in sentences)
In [88]:
# Apply to dataframe
df["well_formed_paragraph"] = df["content_removed_unwanted_elements"].apply(
    lambda x: is_well_formed_paragraph(x) if isinstance(x, str) else False
)
In [89]:
# Compute percentage of well-formed paragraphs
percentage = df["well_formed_paragraph"].mean() * 100
print(f"Porcentaje de noticias con párrafos bien formados: {percentage:.2f}%")
Porcentaje de noticias con párrafos bien formados: 44.67%
In [93]:
# Compute counts per country
df_grouped = df.groupby("country")["well_formed_paragraph"].mean().reset_index()
df_grouped["well_formed_pct"] = df_grouped["well_formed_paragraph"] * 100
df_grouped["not_well_formed_pct"] = 100 - df_grouped["well_formed_pct"]

for i, row in df_grouped.iterrows():
    labels = ["Bien formados", "No bien formados"]
    sizes = [row["well_formed_pct"], row["not_well_formed_pct"]]

    plt.figure(figsize=(6,6))
    plt.pie(sizes, labels=labels, autopct="%1.1f%%", startangle=90, colors=["#66c2a5","#fc8d62"])
    plt.title(f"Párrafos bien formados en noticias de {row['country']}")
    plt.show()
No description has been provided for this image
No description has been provided for this image
In [95]:
# Compute mean well-formed paragraphs per country and category
df_grouped = (
    df.groupby(["country", "category"])["well_formed_paragraph"]
    .mean()
    .reset_index()
)

df_grouped["well_formed_pct"] = df_grouped["well_formed_paragraph"] * 100
df_grouped["not_well_formed_pct"] = 100 - df_grouped["well_formed_pct"]

categories = df_grouped["category"].unique()

for category in categories:
    subset = df_grouped[df_grouped["category"] == category]

    fig, axes = plt.subplots(1, 2, figsize=(12,6))
    fig.suptitle(f"Párrafos bien formados – Categoría: {category}", fontsize=16)

    for ax, (idx, row) in zip(axes, subset.iterrows()):
        labels = ["Bien formados", "No bien formados"]
        sizes = [row["well_formed_pct"], row["not_well_formed_pct"]]
        ax.pie(sizes, labels=labels, autopct="%1.1f%%", startangle=90, colors=["#66c2a5","#fc8d62"])
        ax.set_title(row["country"])

    plt.tight_layout()
    plt.show()
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image
No description has been provided for this image

Paso 4.2: ¿Qué tan frecuentes son los encabezados útiles para formar preguntas?¶

In [96]:
# Asegurarnos de que los títulos sean strings
df["title"] = df["title"].astype(str)
In [97]:
def is_useful_title(text):
    doc = nlp(text)
    # Útil si hay un verbo o entidad nombrada
    has_verb = any(tok.pos_ == "VERB" for tok in doc)
    has_entity = any(ent.label_ in ["PER", "LOC", "ORG", "MISC"] for ent in doc.ents)
    return has_verb or has_entity

df["useful_title"] = df["title"].apply(is_useful_title)
In [98]:
freq_useful = df["useful_title"].mean() * 100
print(f"Porcentaje de títulos útiles: {freq_useful:.2f}%")
Porcentaje de títulos útiles: 98.21%
In [99]:
sizes = [
    df["useful_title"].sum(),
    len(df) - df["useful_title"].sum()
]
labels = ["Útil", "No útil"]
colors = ["#66c2a5","#fc8d62"]

plt.figure(figsize=(6,6))
plt.pie(sizes, labels=labels, autopct="%1.1f%%", startangle=90, colors=colors)
plt.title("Frecuencia de títulos útiles")
plt.show()
No description has been provided for this image

Paso 4.3: ¿Cuántas entidades nombradas (personas, lugares, fechas, organizaciones) hay por texto?¶

In [100]:
# Count entities per text
df["num_entities"] = df["entities"].apply(lambda x: len(x) if isinstance(x, list) else 0)

# Quick summary
print(df[["num_entities"]].describe())
       num_entities
count   5635.000000
mean      17.469033
std       21.988457
min        0.000000
25%        5.000000
50%       11.000000
75%       22.000000
max      335.000000
In [101]:
plt.figure(figsize=(10,6))
sns.histplot(df["num_entities"], bins=20, kde=False)
plt.xlabel("Número de entidades nombradas por texto")
plt.ylabel("Cantidad de noticias")
plt.title("Distribución de entidades nombradas por texto")
plt.show()
No description has been provided for this image
In [102]:
# Count entities per text (if not already done)
df["num_entities"] = df["entities"].apply(lambda x: len(x) if isinstance(x, list) else 0)

# Aggregate by country and category
agg_df = df.groupby(["country", "category"])["num_entities"].describe().reset_index()
print(agg_df[["country","category","count","mean","std","min","25%","50%","75%","max"]])

# Boxplot per country and category
plt.figure(figsize=(14,7))
sns.boxplot(data=df, x="category", y="num_entities", hue="country")
plt.xticks(rotation=45)
plt.ylabel("Número de entidades por texto")
plt.title("Distribución de entidades nombradas por país y categoría")
plt.legend(title="País")
plt.tight_layout()
plt.show()
     country       category   count       mean        std   min   25%   50%  \
0   Colombia        Cultura  1118.0  21.906977  17.412946   0.0  10.0  19.0   
1   Colombia      Economica  1010.0   9.942574   8.538299   0.0   4.0   8.0   
2   Colombia  Internacional   556.0  18.140288  12.762915   0.0   8.0  16.0   
3   Colombia       Judicial   112.0   5.696429   4.553663   0.0   3.0   5.0   
4   Colombia       Politica   714.0  13.920168  12.302091   0.0   5.0  12.0   
5   Colombia          Salud   463.0   7.827214   7.269425   0.0   3.0   6.0   
6   Colombia      Seguridad     7.0  20.428571  11.296860  10.0  13.0  17.0   
7     México        Cultura   187.0   9.128342  15.802702   0.0   2.0   5.0   
8     México      Economica   200.0   6.785000   8.343070   0.0   1.0   4.0   
9     México  Internacional   449.0  51.204900  46.323618   0.0   9.0  46.0   
10    México       Judicial    96.0   5.895833   4.848177   0.0   3.0   5.0   
11    México       Politica   549.0  15.493625  14.866144   0.0   5.0  13.0   
12    México          Salud   140.0  20.371429  33.529831   0.0   2.0   4.0   
13    México      Seguridad    34.0  43.970588  39.188345   0.0   6.5  45.0   

      75%    max  
0   28.00  170.0  
1   14.00   85.0  
2   26.00   97.0  
3    7.00   33.0  
4   19.00  122.0  
5   11.00   55.0  
6   23.50   43.0  
7   10.50  170.0  
8    9.00   60.0  
9   77.00  335.0  
10   7.25   33.0  
11  22.00  195.0  
12  32.25  194.0  
13  68.75  123.0  
No description has been provided for this image

Paso 4.4: ¿Qué tipos de entidades predominan (PERSON, LOC, ORG, DATE, EVENT)?¶

In [104]:
# Ensure 'entities' column has format [(text, type), ...]
df_entities = df.explode("entities").dropna(subset=["entities"])
df_entities["entity_text"] = df_entities["entities"].apply(lambda x: x[0] if isinstance(x, tuple) else None)
df_entities["entity_type"] = df_entities["entities"].apply(lambda x: x[1] if isinstance(x, tuple) else None)

# Count occurrences per entity type (overall)
entity_counts = df_entities.groupby("entity_type")["entity_text"].count().reset_index()
entity_counts = entity_counts.sort_values(by="entity_text", ascending=False)
print(entity_counts)

# Plot overall
plt.figure(figsize=(10,6))
sns.barplot(data=entity_counts, x="entity_type", y="entity_text", hue="entity_text", palette="viridis", legend=False)
plt.ylabel("Número de entidades")
plt.xlabel("Tipo de entidad")
plt.title("Tipos de entidades nombradas más frecuentes")
plt.show()

# Optional: By country
entity_counts_country = df_entities.groupby(["country","entity_type"])["entity_text"].count().reset_index()
plt.figure(figsize=(12,6))
sns.barplot(data=entity_counts_country, x="entity_type", y="entity_text", hue="country", palette="Set2", legend=False)
plt.ylabel("Número de entidades")
plt.xlabel("Tipo de entidad")
plt.title("Tipos de entidades nombradas por país")
plt.show()
  entity_type  entity_text
3         PER        51115
0         LOC        24926
1        MISC        11774
2         ORG        10623
No description has been provided for this image
No description has been provided for this image

Paso 4.5: ¿Qué porcentaje de oraciones contiene información factual que podría servir como respuesta?¶

In [112]:
# Function to check if a sentence is factual
def is_factual(sentence, entities=[]):
    """
    Simple heuristic:
    - A sentence is factual if it contains a named entity or a number/date
    """
    if not sentence:
        return False
    for ent in entities:
        if isinstance(ent, tuple) and ent[0] in sentence:
            return True
    if any(char.isdigit() for char in sentence):
        return True
    return False

# Prepare a DataFrame to store results
rows = []

for country in df["country"].unique():
    for category in df["category"].unique():
        subset = df[(df["country"] == country) & (df["category"] == category)]
        percentages = []
        for _, row in subset.iterrows():
            sentences = row.get("content_sentence_tokenization", [])
            entities = row.get("entities", [])
            if not isinstance(sentences, list) or not sentences:
                continue
            factual = [s for s in sentences if is_factual(s, entities)]
            percentages.append(len(factual)/len(sentences))
        if percentages:
            rows.append({
                "country": country,
                "category": category,
                "factual_percentage": sum(percentages)/len(percentages) * 100
            })

plot_df = pd.DataFrame(rows)

# Plot with Seaborn using standard deviation as confidence interval
plt.figure(figsize=(14,7))
sns.barplot(
    data=plot_df,
    x="category",
    y="factual_percentage",
    hue="country",
    errorbar='sd',
    palette="Set2"
)
plt.ylabel("Porcentaje promedio de oraciones factuales (%)")
plt.xlabel("Categoría")
plt.title("Promedio de oraciones con información factual por país y categoría")
plt.ylim(0,100)
plt.xticks(rotation=45)
plt.legend(title="País")
plt.tight_layout()
plt.show()
No description has been provided for this image
In [113]:
# Overall average factual percentage across all texts
overall_avg = plot_df["factual_percentage"].mean()
print(f"Porcentaje promedio de oraciones factuales en todos los textos: {overall_avg:.2f}%")
Porcentaje promedio de oraciones factuales en todos los textos: 55.48%